passing Collection of abstract class as parameter in function - c#

I want to pass ICollection<AbstractClass> in function as parameter. But when I call it with Collection of concrete types Visual Studio show me error that
method has some invalid arguments
My function is :
private void GenerateId(ICollection<BaseEntity> entities)
{
foreach (BaseEntity e in entities)
{
e.Id = _baseDao.GetNextId();
}
}
My call is :
GenerateId(entity.TitleAdmRegions);
Type of AdmRegions:
public virtual ICollection<TitleAdmRegion> TitleAdmRegions { get; set; }
And AdmRegion is:
public partial class TitleAdmRegion : BaseEntity
{
//...
}

You have to do an explicit cast - in fact, there's no guarantee that a collection of T, where T inherits from U is also a collection of U. Of course, it most likely will be, but...
The relation is called covariance - the ability to use a more specific type in generic "call" instead of its ancestor. MSDN has a nice article on the topic in C# - http://msdn.microsoft.com/en-us/library/dd799517(v=vs.110).aspx
The type-safe way is actually quite simple using generics:
private void GenerateId<T>(ICollection<T> entities)
where T: BaseEntity
{
foreach (var e in entities)
{
e.Id = _baseDao.GetNextId();
}
}
Also, while ICollection<T> is not covariant, IEnumerable<T> is. So another simple way would be to use IEnumerable<BaseEntity> as the parameter:
private void GenerateId<T>(IEnumerable<T> entities) { ... }

The interface ICollection<T> is not covariant in T. It couldn't be because the type contains methods such as void Add(T item). We have that
a TitleAdmRegion is a BaseEntity
but without covariance, that does not imply that
an ICollection<TitleAdmRegion> is an ICollection<BaseEntity>
as you seem to think. The solution is to switch to an interface that is covariant in its type argument. You can use either IEnumerable<out T> or IReadOnlyCollection<out T>. Covariance means that an IEnumerable<TitleAdmRegion> is an IEnumerable<BaseEntity>, and an IReadOnlyCollection<TitleAdmRegion> is an IReadOnlyCollection<BaseEntity>. So change the signature to:
private void GenerateId(IEnumerable<BaseEntity> entities) // or IReadOnlyCollection<BaseEntity>, or IReadOnlyList<BaseEntity>, etc.
{
foreach (BaseEntity e in entities)
{
e.Id = _baseDao.GetNextId();
}
}
and all will be fine.
Covariance (and contravariance) in generics was new in .NET 4.0 (2010). The interface IReadOnlyCollection<out T> was new in .NET 4.5 (2012). Note that collections that allow both reading and writing (such as the List<T> class and the T[] array type) do implement IReadOnlyCollection<out T>.

Related

Why the interface IOrderedEnumerable<T> isn't covariant in T?

I was Looking at the declaration of IOrderedEnumerable an I was suprised that it isn't covariant in it's TElement type parameter .
public interface IOrderedEnumerable<TElement> : IEnumerable<TElement>, IEnumerable
{
IOrderedEnumerable<TElement> CreateOrderedEnumerable<TKey>(Func<TElement, TKey> keySelector, IComparer<TKey> comparer, bool descending);
}
What's the reason for which it was not made covariant ?
It's an oversight and that was fixed in .NET Core. Here is (closed) issue about that and here is pull request which fixes it.
It's not getting fixed in full .NET version I think because that's a breaking change. For example (idea is taken from this answer which is about another breaking change, but applies here too):
public class Base
{
public void DoSomething(IOrderedEnumerable<string> strings)
{
Console.WriteLine("Base");
}
}
public class Derived : Base
{
public void DoSomething(IOrderedEnumerable<object> objects)
{
Console.WriteLine("Derived");
}
}
Then you call
Derived d = new Derived();
d.DoSomething(new List<string>().OrderBy(c => c));
If IOrderedEnumerable is not covariant - Base method would be called. Now suppose we change that to covariant. When we next time compile this code, suddenly Derived method is called.

Generic constraint ignores co-variance

Let's say we have an interface like
public interface IEnumerable<out T>
{ /*...*/ }
that is co-variant in T.
Then we have another interface and a class implementing it:
public interface ISomeInterface {}
public class SomeClass : ISomeInterface
{}
Now the co-variance allows us to do the following
IEnumerable<ISomeInterface> e = Enumerable.Empty<SomeClass>();
So a IEnumerable<SomeClass> is assignable to a variable (or method parameter) of type IEnumerable<ISomeInterface>.
But if we try this in a generic method:
public void GenericMethod<T>(IEnumerable<T> p) where T : ISomeInterface
{
IEnumerable<ISomeInterface> e = p;
// or
TestMethod(p);
}
public void TestMethod(IEnumerable<ISomeInterface> x) {}
we get the compiler error CS0266 telling us that an IEnumerable<T> cannot be converted to an IEnumerable<ISomeInterface>.
The constraint clearly states the T is derived from ISomeInterface, and since IEnumerable<T> is co-variant in T, this assignment should work (as shown above).
Is there any technical reason why this cannot work in a generic method? Or anything I missed that makes it too expensive for the compiler to figure it out?
Change your GenericMethod and add generic constraint class:
public void GenericMethod<T>(IEnumerable<T> p) where T : class, ISomeInterface
{
IEnumerable<ISomeInterface> e = p;
// or
TestMethod(p);
}
Covariance does not support structs, so we need to tell that we want to use classes only.

Action<List<Something>> contravariance limitation

I'm writing a networked application where objects are requested by id and are returned via a delegate callback:
public static void requestById<ModelType>(T id, Action<ModelType> callback)
where ModelType : AbstractModel<T>
{
}
For convenience I have a method for requesting multiple objects at a time:
public static void requestByIds<ModelType>
(List<T> ids, Action<List<ModelType>> callback)
where ModelType : AbstractModel<T>, new()
{
}
Further down the line I have an abstract model object that has multiple children, and a method for requesting it's children:
public abstract void requestSections(Action<List<AbstractSection>> callback);
And then an implementation in a concrete class:
public override void requestSections(Action<List<AbstractSection>> callback)
{
Section.requestByIds<Section>(this.sectionIds, callback);
}
Suddenly I find the delegate
Action<List<AbstractSection>>
is incompatible with
Action<List<Section>>
Is this a limitation of contravariance in C#? Is there any kind of workaround so I can get my override method to work? Thanks
List is invariant, it is not covariant or contravariant.
If you use an IEnumerable<T> rather than a list, than you can rely on IEnumerable<T> being covariant with repsect to T, which makes Action<IEnumerable<T>> contravariant with respect to T.

Contravariant Value Types

I have created this interface for my Repositories.
public interface IRepository<T, in TKey> where T: class
{
IEnumerable<T> Find(Expression<Func<T, bool>> predicate);
IEnumerable<T> FindAll();
T FindSingle(TKey id);
void Create(T entity);
void Delete(T entity);
void Update(T entity);
}
The FindSingle method accepts an ID, which will be used for searching on Primary Key. By using in I expected that I would only be allowed to pass a reference type as TKey. Out of curiosity I decided to create a concrete class and specify it as an int, so I could see the exception.
I looked up MSDN and it specifies this should not work
Covariance and contravariance in generic type parameters are supported for reference types, but they are not supported for value types.
The class I created looks like this
public class ProjectRepository : IRepository<Project,int>
{
public IEnumerable<Project> Find(Expression<Func<Project, bool>> predicate)
{
throw new NotImplementedException();
}
public IEnumerable<Project> FindAll()
{
throw new NotImplementedException();
}
public Project FindSingle(int id)
{
throw new NotImplementedException();
}
public void Create(Project entity)
{
throw new NotImplementedException();
}
public void Delete(Project entity)
{
throw new NotImplementedException();
}
public void Update(Project entity)
{
throw new NotImplementedException();
}
}
Why did I not get an exception on build having specified TKey as a value type? Also, If I removed the in from my parameter what have I lost? the MSDN document says that the contravariance allows using a less derived type, but surely by removing in I can pass any type in as it is still generic.
This is maybe displaying a lack of understanding on contravariance and covariance but it has me a little confused.
Covariance and contravariance don't make as much sense on value types, because they are all sealed. Though it's not clear from the documentation, it is valid to use a struct as a co/contravariant type, it's just not always useful. The documentation you reference is most likely referring to that the following is not valid:
public struct MyStruct<in T>
Contravariance means that you can do something like the following example:
IRepository<string, Base> b = //something
IRepository<string, Derived> d = b;
Since there's nothing that derives from int, you can use an IRepository<string, int>, but only as an IRepository<string, int>.
Covariance means that you can do the reverse, e.g. IEnumerable<T> is out T, which is covariant. You can do the following:
IEnumerable<Derived> d = //something
IEnumerable<Base> b = d;
If you're trying to restrict both TKey and T to classes (reference types), you should include a second restriction:
public interface IRepository<T, in TKey>
where T : class
where TKey : class
Indeed, you are missing the whole point of co- and contravariance :-) It is about being able to assign a variable of a generic type to another variable of the same generic type but with differing generic type argument(s) that are related to the ones used in the source.
Depending on whether the generic type parameter is co- or contravariant, different assignments are allowed.
Assume the following interface:
public interface IRepository<in T>
{
void Save(T value);
}
Additionally, assume the following interface along with a value type and a reference type that implement it:
public interface IBar
{
}
public struct BarValueType : IBar
{
}
public class BarReferenceType : IBar
{
}
Finally, assume two variables:
IRepository<BarReferenceType> referenceTypeRepository;
IRepository<BarValueType> valueTypeRepository;
Contravariance now means that you can assign an instance of IRepository<IBar> to the variable referenceTypeRepository, because BarReferenceType implements IBar.
The section from the MSDN you quote simply means that the assignment of an instance of IRepository<IBar> to valueTypeRepository is not legal, although BarValueType also implements IBar.
There is no problem in implementing your interface with a value type. You will only get an error when trying to assign an IRepository<Project, object> to a IRepository<Project, int>, for example. In the following code, the last assignment won't compile:
public interface IContravariant<T, in TKey> where T : class
{
T FindSingle(TKey id);
}
public class objCV : IContravariant<Project, object>
{
public Project FindSingle(object id)
{
return null;
}
public static void test()
{
objCV objcv = new objCV();
IContravariant<Project, Project> projcv;
IContravariant<Project, int> intcv;
projcv = objcv;
intcv = objcv;
}
}
In this article, they are telling us that the type parameter is treated as invariant by the compiler:
Variance applies only to reference types; if you specify a value type
for a variant type parameter, that type parameter is invariant for the
resulting constructed type.
From: http://msdn.microsoft.com/en-us/library/dd799517.aspx

C# Generics not working as expected

We have a problem with the usage of generics.
We have a generic collection of generic keyvalue pair which is defined as follows
public class KeyValueTemplate<K, V> : IGetIdentifier<K>
{
//...
}
public class KeyValueListTemplate<K, V> : ObservableCollection<KeyValueTemplate<K, V>>
{
//....
}
public class KeyValueStringListTemplate : KeyValueListTemplate<string,string> { }
We are using this in the code as follows
public class test
{
public KeyValueStringListTemplate SetValuesList{get;set;}
public ObservableCollection<IGetIdentifier<string>> GetList()
{
return SetValuesList;
}
}
The complier is not accepting this. The error is
Cannot convert type 'KeyValueStringListTemplate' to 'System.Collections.ObjectModel.ObservableCollection<IGetIdentifier<string>>
Why?? Both the types are same to me.
This line
public class KeyValueListTemplate<K, V> : ObservableCollection<KeyValueTemplate<K, V>>
defines a new type, KeyValueListTemplate, that is a subtype of ObservableCollection, so they are different types. KeyValueListTemplatecan be safely converted to ObservableCollection, because it has a superset of ObservableCollection's functionality (by Liskov Substitution Principle), but the opposite conversion is not safe.
Its a matter of covariance in generics, which was not exposed to c#/vb.net before .net 4.
While it seem trivial that you can do this:
IEnumerable<string> strings = new List<string>();
// An object that is instantiated with a more derived type argument
// is assigned to an object instantiated with a less derived type argument.
// Assignment compatibility is preserved.
IEnumerable<object> objects = strings;
which is what your code is doing at the bottom line, it wasnt supported up to .net 4
The article i linked to explanis how to implement it and how it works.

Categories

Resources