Reputation: 660
For learning purposes, I am trying to make my own simple database in C#. I have written the code to store and load a table (Class "Pizzas") already. Simple, because the logic is hard coded - it only supports instances of the "Pizzas" type. No I want to generalize my database logic. This is what I have done so far (simplified):
Please note: The only reason I have an Abstract class is because in class MyDB, I could not type
List<MyTable<T>> _tables;
Is there a better approach to hold my tables in a list? A Interface instead?
public class MyDB
{
public List<MyTable> _tables;
public MyDB() {_tables = new List<MyTable>();}
public void AddTable(MyTable table) {_tables.Add(table);}
}
public abstract class MyTable
{
protected MyTableDefinition _tableDefinition;
public abstract void Save();
}
class MyTable<T> : MyTable
{
public List<T> _rows;
public MyTable()
{
_rows = new List<T>();
_tableDefinition = new MyTableDefinition(typeof(T));
}
internal void AddRecord(T record) {_rows.Add(record);}
internal void RemoveRecord(T record) {_rows.Remove(record);}
public override void Save() {Debug.WriteLine("Work in Progress...");}
}
public class MyTableDefinition { /* bla bla */ }
This works actually as I intended. Now, my issue is with my class that is supposed to handle the reading and writing from disk. I thought it might be a good idea to have it seperate so I can handle different platforms (regular file, IsolatedStorage, Storagefile, etc.).
public static class MyIOforFile
{
List<Stream> _tableStreams;
public static Test(MyDB database)
{
string tmpPath = @"C:\tmp";
foreach (var table in database._tables)
{
using (var file = new BinaryWriter(File.Open(tmpPath + @"\SampleOutput.txt", FileMode.Create)))
{
// Problem: Can't access _rows here.
foreach (var item in table._rows)
{
// ...
}
}
}
}
}
Note: Is Reflection the only way to access the _rows in my case? Is there an appropriate way to handle my scenario (not a big fan of Reflection) in OOP?
An this is how I call the code:
var testdb = new MyDB();
var PizzaTable = new MyTable<PizzaItem>();
testdb.AddTable(PizzaTable);
MyIOLayer.Test(testdb);
Thanks for having a look!
Upvotes: 3
Views: 1978
Reputation: 902
Here is the solution I'd use. First, prefer interfaces to abstract classes especially if there is very little shared logic. Second, define your common logic (things you do while iterating) in your interface. Finally, keep your fields private and create appropriate methods for interacting with them (see example ForEachRow
below:
These are the interfaces:
public interface ITable
{
MyTableDefinition TableDef { get; }
void Print();
void ForEachRow(Action<IRow> action);
}
public interface IRow
{
void Print();
}
And these are examples of how to use them:
public class MyTable<T> : ITable where T : IRow
{
private List<T> _rows;
private MyTableDefinition _tableDef;
public MyTable() { ... }
public MyTableDefinition TableDef { get { return _tableDef; } }
public void Print() { ForEachRow(row => row.Print()); }
public void ForEachRow(Action<IRow> action)
{
foreach (T row in _rows)
{
action(row);
}
}
}
public class MyRow : IRow
{
private int _whateverFieldsYouWant;
public void Print() { ... }
}
Upvotes: 1
Reputation: 888233
You've encountered a hard problem: You have a generic collection of an unknown generic type, and you want to process it without access to the generic type.
You can solve this by exposing a collection of a base type using covariance:
public abstract class MyTable {
public abstract IEnumerable<object> UntypedRows { get; }
}
public class MyTable<T> : MyTable {
public override IEnumerable<object> UntypedRows { get { return _rows; } }
}
If you have a common base type for all of your rows, you can constrain T
to inherit that type, then change object
to that type.
Obviously, you won't be able to access anything that isn't in the common type unless you know what the type parameter is.
Alternatively, you can move the work to the generic class:
public abstract class MyTable {
public abstract void SaveRows(...);
}
public class MyTable<T> : MyTable {
public abstract void SaveRows(...) {
// Write code here, with access to the generic type parameter
}
}
Upvotes: 1