2

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?

Alexei Levenkov
  • 98,904
  • 14
  • 127
  • 179
Orlando
  • 935
  • 2
  • 20
  • 42
  • 3
    Is that the _exact_ error message? I would expect a message that it cannont convert `MyObject` to `MyObject` since `BaseModel` is invariant. – D Stanley Apr 06 '16 at 16:42
  • No the exact error is: cannot convert from 'ProjectANamespace.MyObject' to 'ProjectANamespace.MyObject' I included the namespace to show the objects defined in the different projects – Orlando Apr 06 '16 at 16:46
  • 1
    Yes that makes much more sense. Then the problem is due to `MyObject` being invariant, not because it's not "visible" to the main program. – D Stanley Apr 06 '16 at 16:55

2 Answers2

4

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>>).

Luaan
  • 62,244
  • 7
  • 97
  • 116
0

To accomplish this, I believe that you need to use the Covariance features of c# 4+. In particular, I believe you need to use the out modifier. After some testing, I discovered that this will not work for classes. However, if you introduce an interface into the pattern, you could make this work. For example, the following compiles

public class Base
{

}

public class Derived : Base
{

}

public interface IWrapper<out T> where T : Base
{

}

public class Wrapper<T> : IWrapper<T> where T : Base
{

}

public static class Methods
{
  public static void Test()
  {
    var obj = new List<IWrapper<Base>>();
    obj.Add(new Wrapper<Derived>());
  }
}
erdomke
  • 4,980
  • 1
  • 24
  • 30
  • 3
    This won't work because classes are invariant and the `T` parameter in the `Func` is contravariant anyway. – Lee Apr 06 '16 at 16:44
  • Very true. I just tested it and discovered that. I will edit to match. – erdomke Apr 06 '16 at 16:47