Reputation: 1950
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.
Upvotes: 5
Views: 3403
Reputation: 36618
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.
Upvotes: 1
Reputation: 415735
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;
}
Upvotes: 0
Reputation: 2324
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.
Upvotes: 3
Reputation: 59653
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.
Upvotes: 0
Reputation: 8181
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.
Upvotes: 1
Reputation: 25099
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.
Upvotes: 0
Reputation: 18295
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;
}
}
Upvotes: 3
Reputation: 1720
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.
Upvotes: 0
Reputation: 1702
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.
Upvotes: 0
Reputation: 15011
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...
Upvotes: 2