Search code examples
c#linqgenericstypesgeneric-programming

Conversion fails between two Generic Types


I am trying to create an object type (MyObject) with a linq expression of type T. My class states that the value of T must be of type BaseModel (which is an object created by me). Below is how MyObject is constructed:

public class MyObject<T> where T : BaseModel
{
    public Type MyType;

    public Expression<Func<T, bool>> MyExpression;
}

All of my models inherit from BaseModel. Example:

public class MyModel : BaseModel
{
    public string Name { get; set; }
}

My object is used inside of a generic static class:

public static class MyStaticClass
{
    private static Dictionary<MyObject<BaseModel>, string> MyDictionary = new Dictionary<MyObject<BaseModel>, string>();

    public static AddMyObjectsToDictionary(List<MyObject<BaseModel>> myObjects)
    {
        //CODE
    }

    //REST OF CODE
}

Then when my app loads it does the following (Error is thrown here):

List<MyObject<BaseModel>> myObjects = new List<MyObject<BaseModel>>();

myObjects.Add(new MyObject<MyModel>()
{
    MyType = typeof(MyModel),
    MyExpression = p => p.Name == "myname"
});

MyStaticClass.AddMyObjectsToDictionary(myObjects);

Exact error message thrown with the namespaces to show in which project each object is located:

cannot convert from 'ProjectANamespace.MyObject<ProjectBNamespace.MyModel>' to 'ProjectANamespace.MyObject<ProjectANamespace.BaseModel>'

I need to be able to create a generic expression within MyModel however I cannot specify MyModel inside of MyStaticClass since it is meant to be a generic class which is located in another project along with BaseModel.

Anyone have any ideas how resolve this issue?


Solution

  • MyObject<MyModel> isn't a MyObject<BaseModel>, just like List<string> isn't a List<object>. It works the same outside of generics too - the general concept is called variance, and describes valid substitution of types. Since MyObject is neither co-variant nor contra-variant with respect to T, MyObject<T> can never be a MyObject<U>, for any T and U that aren't the same.

    Generic type arguments in interfaces and delegates can be both co-variant and contra-variant, but not both at the same time. For example, your Func<T, bool> is contra-variant, because you can substitute a type derived from T instead of T itself. So a Func<BaseModel, bool> can be converted to Func<MyModel, bool> (just as MyModel can be passed as an argument "instead" of BaseModel), but not vice versa. Analogously, Func<T, U> is contra-variant with respect to T and co-variant with respect to U - you can't return object from a function that has a return type of string.

    So even if you changed your definition to use an interface to add variance, you'll only be able to get contra-variance, not the co-variance you want. Too bad - there's no safe way to do that.

    Instead, if you need a non-generic interface, just add a non-generic interface. That's it. Instead of List<MyObject<BaseModel>>, you'll have List<IMyObject>, which you can use as required (probably through casting, or just exposing the simple Expression instead of Expression<Func<T, bool>>).