I'm trying to get a list that can store a DataColumn
that takes in generic T
but each DataColumn can have a different type:
public class DataColumn<T>
{
private string columnName;
private List<T> values;
public readonly Type dataType;
public DataColumn(string columnName, Type dataType)
{
this.columnName = columnName;
this.dataType = dataType;
}
public class DataTable
{
private List<DataColumn<object>> columns;
public bool AddColumn<T>(string name)
{
//DataColumn<T> column = new(name, typeof(T));
DataColumn<object> column = new(name, typeof(T));
columns.Add(column);
length++;
return true;
}
}
The commented line doesn't work.
Why can the object not store generic type T
?
Why is DataColumn<T>
different from DataColumn<object>
?
Why can type T
not evaluate to object
?
One solution is to create an interface IDataColumn
and the list would store it:
public interface IDataColumn
{
Type GetType();
bool Add(dynamic value);
object GetValue(int index);
}
public class DataColumn<T> : IDataColumn{...}
While it does solve the issue, this creates another inconvenience for accessing the attributes. I could create getters but it would return either dynamic
or object
and not the type that the table stores values as because the interface doesn't know it. But I was told to avoid the use of these as they can lead to unsafe checks.
How do I solve this?
The solution could look like this:
public abstract class DataColumnBase { }
public class DataColumn<T> : DataColumnBase {
private string columnName;
private System.Collections.Generic.List<T> values;
public DataColumn(string columnName) {
this.columnName = columnName;
}
// ...
}
public class DataTable {
private System.Collections.Generic.List<DataColumnBase> columns =
new();
public bool AddColumn<T>(string name) {
DataColumn<T> column = new(name);
columns.Add(column);
return true;
}
}
Eventually, you may still need to do the type case, but only on values. Ideally, you can get rid of type casts completely. To do so, you can avoid using arbitrary value types, two. Then you can add an abstract base class ValueBase
for all value types or an interface IValue
to be implemented by all these types, use this base type of interface in the generic constraints of T
, and never cast values to their runtime types. Instead, operate with those values only through their IValue
or ValueBase
members overridden in all runtime value classes. That would be a pure OOP approach.
Now, I'll try to answer your questions.
Why can the object not store generic type T?
You can, in the form of System.Type
. Yes, System.Type
can represent generic types, too, but a generic type cannot be a type of any object. You can store an object of the System.Type
only for the object types you can define, and it won't make sense in the case of your code. A generic type is not a type in the following sense: you cannot create an object of this type. You can only instantiate a generic type to make a complete type out of it, and only then create an object of this complete type. Don't mess things up: you can get a runtype type of any object using object.GetType()
, but this runtime type is not a generic type.
Why is
DataColumn<T>
different fromDataColumn<object>
?
DataColumn<object>
can be a real object in memory. DataColumn<T>
cannot be an object, because this is just a declaration based on the generic type parameter T
. It can become an object only when a generic method or class is instantiated with one of the complete types based on T
.
Why can type
T
not evaluate to object?
Because there are no objects of T
in nature. T
is not a type, it is a generic type parameter, a formal parameter. It becomes a type only after the instantiation of the generic type when an actual type parameter substitutes T
.