I currently use the following base collection.
abstract class BaseCollection : Collection<BaseRecord>
{
}
I would like to replace it with the generic collection below. This however is not a trivial task due to the amount of derived classes that currently implement BaseCollection.
abstract class BaseCollection<TRecord> : Collection<TRecord> where TRecord : BaseRecord,new()
{
}
Instead of a massive overhaul I’d like to slowly introduce this new collection.
abstract class BaseCollection<TRecord> : BaseCollection,IEnumerable<TRecord> where TRecord : BaseRecord,new()
{
public new IEnumerator<TRecord> GetEnumerator()
{
return Items.Cast<TRecord>().GetEnumerator();
}
}
While I can enumerate over the collection using a foreach, the LINQ statement below does not compile. Is this possible? I realize this is a bit of hack but I’m not sure how else to get this done.
class Program
{
static void Main(string[] args)
{
DerivedCollection derivedCollection = new DerivedCollection
{
new DerivedRecord(),
new DerivedRecord()
};
foreach (var record in derivedCollection)
{
record.DerivedProperty = "";
}
var records = derivedCollection.Where(d => d.DerivedProperty == "");
}
}
Here are the two records used above. Thanks.
class DerivedRecord : BaseRecord
{
public string DerivedProperty { get; set; }
}
abstract class BaseRecord
{
public string BaseProperty { get; set; }
}
Here is the derived collection.
class DerivedCollection : BaseCollection<DerivedRecord>
{
}
Foreach uses some “magic” to figure out what type to loop over. It doesn’t require the collection to support
IEnumerableof anything. This probably explains the difference.The problem is that the compiler can’t figure out the generic type parameter for
Where. If you do this, it will work:No casts are used, but the set of available choices has been reduced down to one.
Or you can specify the type parameter explicitly:
I think to solve your problem you’re going to have to stop
BaseCollectionfrom inheriting another version ofIEnumerable<T>:There I’m still instantiating
Collection<T>but as a separate object instead of a base class. Then forwarding things on to it to mimic its API as necessary.The derived generic version needs to completely implement
IEnumerable<T>.Then the rest of your sample code works fine without the workarounds I posted initially, because it only has one
IEnumerable<T>implementation to work with.Update, more details
Here’s a really slimmed down version of the same situation, breaking all links with
IEnumerable, declaring everything ourselves from scratch, so we can see what’s happening.R1andR2are like the two record classes. In your version,R2inheritsR1– but that’s not relevant to this issue, so I’ve omitted it.I<T>is the generic interface, standing in forIEnumerable<T>. But my version has no methods! They also aren’t relevant to this issue.Then I’ve collapsed your collection class inheritance system down to just two layers. The base class
C1implementsI<T>, and then a derived classC2chooses to askC2to implementI<R1>and then also implementsI<R2>directly. The number of layers makes no difference either. Nor do type constraints declared withwhere, so they are also omitted here.The upshot is that
C2has two implementations ofI<T>:I<R1>andI<R2>. One it inherits fromC1, the other it adds itself.Finally I’ve got a method
M, which stands in forWherein Linq. It doesn’t need to be an extension method to show the issue, so I’ve made it an ordinary static method for clarity.So when we come to call our method
M, the compiler has to figure out whatTis. It does this by looking at what we’ve passed as the only parameter to the method, which has to supportI<T>. Unfortunately, we’re passing something that supportsI<R1>andI<R2>, so how can the type inference process make a choice between them? It can’t.As my interface has no methods, clearly putting
newin front of a method isn’t going to help me, and that’s why it doesn’t help you. The issue is not deciding which method in an interface to call, but whether to treat the argument toMasI<R1>orI<R2>.Why doesn’t the compiler report this as a type inference problem? According to the C# 3.0 spec, it just doesn’t – type inference runs first, to produce a set of available overloads, and then overload resolution runs to pick the best choice. If type inference cannot decide between two possible expansions of a generic method, it eliminates both, so overload resolution never even sees them, so the error message says there isn’t any applicable method called
M.(But if you have Resharper, it has its own compiler that it uses to give more detailed errors in the IDE, and in this case it says specifically: “The type arguments for method M cannot be inferred from the usage”.)
Now, why is
foreachdifferent? Because it’s not even type safe! It dates back to before generics were added. It doesn’t even look at interfaces. It just looks for a public method calledGetEnumeratorin whatever type it loops through. For example:That’s a perfectly good collection as far as the compiler is concerned! (Of course it will blow up at runtime because it returns a null
IEnumerator.) But note the absence ofIEnumerableor any generics. This means thatforeachdoes an implicit cast.So to relate this to your code, you have a “down cast” from
BaseRecordtoDerivedRecord, which you implement with theCastoperator from Linq. Well,foreachdoes that for you anyway. In my example above,Cis effectively a collection of items of typeobject. And yet I can write:The compiler happily inserts a silent cast from
objecttostring. Those items could be anything at all… Yuck!This is why the advent of
varis great – always usevarto declare yourforeachloop variable, and that way the compiler will not insert a cast. It will make the variable be of the most specific type it can infer from the collection at compile time.