tags:

views:

1144

answers:

10

I'm trying to grasp the concept of .NET Generics and actually use them in my own code but I keep running into a problem.

Can someone try to explain to me why the following setup does not compile?

public class ClassA
{
 ClassB b = new ClassB();

 public void MethodA<T>(IRepo<T> repo) where T : ITypeEntity
 {
  b.MethodB(repo);
 }
}

public class ClassB
{
 IRepo<ITypeEntity> repo;

 public void MethodB(IRepo<ITypeEntity> repo)
 {
  this.repo = repo;
 }
}

I get the following error:
cannot convert from IRepo<'T> to IRepo<'ITypeEntity>

MethodA gets called with a IRepo<'DetailType> object parameter where DetailType inherits from ITypeEntity.

I keep thinking that this should compile as I'm constraining T within MethodA to be of type ITypeEntity.

Any thoughts or feedback would be extremely helpful.

Thanks.

Edit: Nick R has a great suggestion but unfortunately in my context, I don't have the option of making ClassA Generic. ClassB could be though.

+2  A: 

The problem is a tricky one to get your head around. DetailType may inherit from ITypeEntity, but isn't actually ITypeEntity. Your implementation of DetailType could introduce different functionality, so DetailType implements ITypeEntity but isn't equal to ITypeEntity. I hope that makes sense...

KiwiBastard
A: 

If B is a subclass of A, that does not mean that Class<B> is a subclass of Class<A>. So, for this same reason, if you say "T is an ITypeEntity", that does not mean that "IRepo<T> is an IRepo<ITypeEntity>". You might have to write your own conversion method if you want to get this working.

Smashery
A: 

T is a type variable that will be bound to a partcular type in usage. The restriction ensures that that type will represent a subset of the types that implement ITypeEntity, excluding other types that implement the interface.

Nick Higgs
A: 

at compile time even though you're constraining it the compiler only knows that T in MethodA is a reference type. it doesn't know what type it is constrained to.

Mladen
+3  A: 

Well this compiles ok. I basically redifined the classes to take generic parameters. This may be ok in your context.

public interface IRepo<TRepo>
{
}

public interface ITypeEntity
{
}


public class ClassA<T> where T : ITypeEntity
{
    ClassB<T> b = new ClassB<T>();
    public void MethodA(IRepo<T> repo)
    {
        b.MethodB(repo);
    }
}
public class ClassB<T> where T : ITypeEntity
{
    IRepo<T> repo;
    public void MethodB(IRepo<T> repo)
    {
        this.repo = repo;
    }
}
Nick R
If you're generic'ing up the first class the 2nd will also need to be generic'ed
Slace
Unfortunately I don't have the option to make ClassA a Generic class as it is a ASP .Net User Control and I really don't want to try to figure out that problem. Thanks for your thought though. It was perfectly reasonable based on the limited context I gave.
Gary B
In that case, do you need to use generics? It strikes me the problem is because ClassA contains an object of type ClassB. Try to implement without generics first - that may be adequate.
Nick R
The IRepo interface is Generic. I cannot change that. At least I think I can't. Maybe I'll look into that a little more.
Gary B
A: 

This is a redundant use of generics, if T can only ever be an instance of ITypeEntity you shouldn't use generics.

Generics are for when you have multiple types which can be inside something.

Slace
That is not quite right. T can be any object that implements ITypeEntity. This is a correct use of generics.
Metro
Yes, but because ITypeEntity is an interface you can cast the object back to an ITypeEntity at any point. So to actually pass something into that generic argument it has to implement ITypeEntity, in turn it "is" ITypeEntity
Slace
+1  A: 

I get the following error: cannot convert from IRepo<'T> to IRepo<'ITypeEntity>

You are getting this compilation error because IRepo<T> and IRepo<ITypeEntity> are not the same thing. The latter is a specialization of the former. IRepo<T> is a generic type definition, where the type parameter T is a placeholder, and IRepo<ITypeEntity> is a constructured generic type of the generic type definition, where the type parameter T from is specified to be ITypeEntity.

I keep thinking that this should compile as I'm constraining T within MethodA to be of type ITypeEntity.

The where constraint does not help here because it only contrains the type you can provide for T at the call-sites for MethodA.

Here is the terminology from the MSDN documentation (see Generics in the .NET Framework) that may help:

  • A generic type definition is a class, structure, or interface declaration that functions as a template, with placeholders for the types that it can contain or use. For example, the Dictionary<<K, V> class can contain two types: keys and values. Because it is only a template, you cannot create instances of a class, structure, or interface that is a generic type definition.

  • Generic type parameters, or type parameters, are the placeholders in a generic type or method definition. The Dictionary<K, V> generic type has two type parameters, K and V, that represent the types of its keys and values.

  • A constructed generic type, or constructed type, is the result of specifying types for the generic type parameters of a generic type definition.

  • A generic type argument is any type that is substituted for a generic type parameter.

  • The general term generic type includes both constructed types and generic type definitions.

  • Constraints are limits placed on generic type parameters. For example, you might limit a type parameter to types that implement the IComparer<T> generic interface, to ensure that instances of the type can be ordered. You can also constrain type parameters to types that have a particular base class, that have a default constructor, or that are reference types or value types. Users of the generic type cannot substitute type arguments that do not satisfy the constraints.

Atif Aziz
+1  A: 

Please see @monoxide's question

And as I said there, checking out Eric Lippert's series of posts on contravariance and covariance for generics will make a lot of this clearer.

Hamish Smith
+2  A: 

Inheritance doesn't work the same when using generics. As Smashery points out, even if TypeA inherits from TypeB, myType<TypeA> doesn't inherit from myType<TypeB>.

As such, you can't make a call to a method defined as MethodA(myType<TypeB> b) expecting a myType<TypeB> and give it a myType<TypeA> instead. The types in question have to match exactly. Thus, the following won't compile:

myType<TypeA> a; // This should be a myType<TypeB>, even if it contains only TypeA's

public void MethodB(myType<TypeB> b){ /* do stuff */ }

public void Main()
{
  MethodB(a);
}

So in your case, you would need to pass in an IRepo<ITypeEntity> to MethodB, even if it only contains DetailTypes. You'd need to do some conversion between the two. If you were using a generic IList, you might do the following:

public void MethodA<T>(IList<T> list) where T : ITypeEntity
{
  IList<T> myIList = new List<T>();

  foreach(T item in list)
  {
    myIList.Add(item);
  }

  b.MethodB(myIList);
}

I hope this is helpful.

Lucas Richter
A: 

In the context of wrapping your head around generic methods, allow me to give you a simple generic function. It's a generic equivalent of VB's IIf() (Immediate if), which is itself a poor imitation of the C-style ternary operator (?). It's not useful for anything since the real ternary operator is better, but maybe it will help you understand how generic function are built and in what contexts they should be applied.

T IIF<T>(bool Expression, T TruePart, T FalsePart)
{
    return Expression ? TruePart : FalsePart;
}
Joel Coehoorn