I've got the following structure of classes and I want my generic side to be able to access these two lists but I cannot get the conversion to work. I've tried various permutations of type guards and cast<> calls.
abstract class B {}
class X : B {}
class Y : B {}
class MyList<T> : List<T> where T : B {}
class Data
{
MyList<X> XList;
MyList<Y> YList;
MyList<T> GetList<T>() where T : B
{
if (typeof(T) == typeof(X))
return (MyList<T>)XList;
if (typeof(T) == typeof(Y))
return (MyList<T>)YList;
return new MyList<T>() { };
}
}
Pretty much the only constraint for all of this is that Data needs to be able to be loaded from a Blazor appsettings.json
somehow. Currently the structure looks like this:
{
"Configs": {
"XList": [
{
"Name": "X1",
// more data
},
{
"Name": "X2",
// more data
},
{
"Name": "X3",
// more data
}
],
"YList": [
{
"Name": "Y1",
// more data
},
{
"Name": "Y2",
// more data
}
]
},
}
CodePudding user response:
You have to convince compiler to allow a cast like this, for example:
class Data
{
MyList<X> XList = new MyList<X>();
MyList<Y> YList = new MyList<Y>();
MyList<T> GetList<T>() where T : B
{
// cast to object first, that's always allowed
// then cast to target type, also always allowed
// but obviously might throw at runtime (in general case)
if (typeof(T) == typeof(X))
return (MyList<T>) (object) XList;
if (typeof(T) == typeof(Y))
return (MyList<T>) (object) YList;
return new MyList<T>() { };
}
}
CodePudding user response:
This is another covariance problem. Maybe this question is helpful, or documentation.
Your problem is the compiler is trying to ensure the containers are used correctly. You want to return "the right type", but your code is returning a "higher" typed object, and the compiler is complaining because that will allow mixing the wrong typed entities together. You can change to a covariant (out) type container, and the compiler stops complaining because you can no longer insert in the "wrong" type object.
public class Data
{
IEnumerable<X> XList;
IEnumerable<Y> YList;
IEnumerable<T> GetList<T>() where T : B
{
if (typeof(T) == typeof(X))
return (IEnumerable<T>)XList;
if (typeof(T) == typeof(Y))
return (IEnumerable<T>)YList;
return new List<T>() { };
}
}
How you resolve this depends on your design considerations. Probably IEnumerable
will not work if you need to modify the collection, but maybe safe if this is only loaded once then read. Or you can box to object like the other answer suggests. Or maybe come up with some extension methods based on the type you need, since you are already passing in a type parameter, so you already know what you need.