r3plica
r3plica

Reputation: 13387

Cast Collection<Derived> to Collection<Base>

Got another simple question here that is eluding me.

I have 2 classes:

namespace Assets
{
   public class BaseAsset
   {
       // Code here
   }
}

And

namespace Assets
{
   public class Asset : BaseAsset
   {
       // Code here
   }
}

I have a function that returns a collection of Asset from the database and I want another function to execute that function and return a collection of BaseAsset. I have tried this:

    public static Collection<BaseAsset> GetCategoryAssets(int CategoryId, string UserId, string CompanyId)
    {
        return (Collection<BaseAsset>)AssetData.getAssets(CategoryId, UserId, CompanyId);
    }

but as you can guess, it doesn't work. If I was working with lists, I could do:

    public static List<BaseAsset> GetCategoryAssets(int CategoryId, string UserId, string CompanyId)
    {
        return AssetData.getAssets(CategoryId, UserId, CompanyId).Cast<BaseAsset>().ToList();
    }

But I would prefer to use a collection, can anyone come up with an elegant solution?

Cheers, r3plica

Upvotes: 2

Views: 1831

Answers (5)

SWeko
SWeko

Reputation: 30902

Since the Collection<T> class has a constructor that takes an IList<T> as an argument, you can always do:

Collection<BaseAsset> = new Collection<BaseAsset>(
                                           assetList.Cast<BaseAsset>().ToList());

Of course, if you need to reuse this behaviour, you could make a CastToCollection extension:

public static Collection<TResult> CastToCollection<TResult>(this IEnumerable source)
{
   return new Collection<TResult>(source.Cast<TResult>().ToList());
}

Upvotes: 0

Eric Lippert
Eric Lippert

Reputation: 660289

This is a very frequently asked question. The name of the feature that you want is generic covariance; that is, the feature that says "if a giraffe is a kind of animal then a list of giraffes is a kind of list of animals."

The problem is that a list of giraffes is not a kind of list of animals. You can put a tiger into a list of animals, but you can't put a tiger into a list of giraffes, and therefore a list of giraffes cannot be used in any context where a list of animals is expected.

The reason you should use IEnumerable<T> instead of Collection<T> is because as of C# 4, IEnumerable<T> is covariant in T, provided that the type arguments provided are both reference types. That is, a sequence of strings can be used as a sequence of objects, because both are reference types. But a sequence of ints cannot be used as a sequence of objects, because one is a value type.

The reason this is safe is because there is no way to insert a tiger into an IEnumerable<Giraffe>.

Upvotes: 11

Jean Hominal
Jean Hominal

Reputation: 16796

The problem is that Collection<T> and ICollection<T> are invariant (that is, Collection<BaseAsset> is neither a subtype nor a supertype of Collection<Asset>).

The problem will be very easily solved by returning either IEnumerable<BaseAsset> or IReadOnlyList<BaseAsset> instead of Collection<BaseAsset>.

That is, you can write:

public static IEnumerable<BaseAsset> GetCategoryAssets(int CategoryId, string UserId, string CompanyId)
{
    return AssetData.getAssets(CategoryId, UserId, CompanyId);
}

The cast becomes unnecessary.

In general, you should prefer interface types (such as IList<T>, IReadOnlyList<T>, ICollection<T> or IEnumerable<T>) over concrete types (Collection<T> or List<T>) when specifying return values and function parameters.

Upvotes: 2

dutzu
dutzu

Reputation: 3910

Instead of trying to cast to the base class, why not just extract an interface and use that.

Upvotes: 0

Matti Virkkunen
Matti Virkkunen

Reputation: 65156

If you want the ease of .ToList, just write your own .ToCollection extension method. The implementation should be straightforward - take an IEnumerable<T>, loop through it and add everything into a collection with Add.

Upvotes: 2

Related Questions