问题
Why does this work:
public IList<ICoupon> GetCouponsForSite(string siteSlug)
{
var coupons = _db.Coupons.Where(x => x.Site.slug == siteSlug)
.Select(x => new Coupon(x.id));
var list = new List<ICoupon>();
foreach (var coupon in coupons)
{
list.Add(coupon);
}
return list;
}
But this does does not work (error - cannot convert expression type to return type):
public IList<ICoupon> GetCouponsForSite(string siteSlug)
{
return _db.Coupons.Where(x => x.Site.slug == siteSlug)
.Select(x => new Coupon(x.id)).ToList();
}
回答1:
Because db.Coupons...ToList() returns an IList<Coupon> rather than an IList<ICoupon>. IList<Coupon> does not derive from IList<ICoupon> because C# 3 doesn't support generic variance. (C# 4 does support generic variance, but it still won't derive in this case. Consider that whoever receives an IList<ICoupon> could try to stuff a SomeEvilTypeThatImplementsICoupon into it. But an IList<Coupon> couldn't accept that because SomeEvilTypeThatImplementsICoupon doesn't derive from Coupon. See http://hestia.typepad.com/flatlander/2008/12/c-covariance-and-contravariance-by-example.html for discussion of this convertibility issue albeit in a slightly different context, and the Eric Lippert articles linked from there.)
(Your first snippet, by contrast, explicitly constructs a List<ICoupon>, which can contain anything that implements ICoupon, and then puts some Coupon objects into that list. Now if the receiver decides to poke SomeEvilTypeThatImplementsICoupon into it, all is well, because the List was built to hold any ICoupon, not just actual Coupon objects.)
回答2:
It can't implicitly cast List<Coupon> to List<ICoupon>. Try this:
public IList<ICoupon> GetCouponsForSite(string siteSlug)
{
return _db.Coupons.Where(x => x.Site.slug == siteSlug)
.Select(x => new Coupon(x.id)).Cast<ICoupon>().ToList();
}
The basic reason for this is that if you had for example a class FancyCoupon : ICoupon and tried to put that into a List<Coupon> then it would fail cause FancyCoupon doesn't derive from Coupon (only ICoupon) but it should work just fine into a List<ICoupon>. So while at first glance it looks like it should be able to use one as the other there's rather important differences between the two types.
The cast call essentially iterates over the list and typecasts each one for the new list (There's a bit more to it for performance reasons under the bonnet, but for practical purposes you can think of it that way).
(Updated with fix from comments)
回答3:
IQueryable<ICoupon> is not derived from IList<ICoupon>.
回答4:
This is because the compiler infers ICoupon, and not Coupon, in the Select as generic type argument. So instead of an explicit cast after the Select as proposed by others (which not too efficient because it needs to iterate over all items), you can also use implicit casting (or more correctly variance) by specifying the correct Select generic types:
public IList<ICoupon> GetCouponsForSite(string siteSlug)
{
return _db.Coupons.Where(x => x.Site.slug == siteSlug)
.Select<?, ICoupon>(x => new Coupon(x.id)).ToList();
}
(You need to replace the ? with the appropriate type of the Coupons collection.)
来源:https://stackoverflow.com/questions/1864080/list-of-interfaces-vs-list-of-derived-type-cannot-convert-expression-type-to