Cast Generic≪Derived≫ to Generic≪Base≫

Cast Generic Derived to Generic Base

You cannot cast a Generic<Derived> to a Generic<Base>.

Just imagine if you could. You have a List<Wolf> and cast it to a List<Animal>. Now you could .Add() a Sheep to your List<Animal>. But wait... now your List<Wolf> contains a Sheep. What a mess.

This would only work out if you could make sure that the thing you cast to is read-only in all possible forms. This is was co- and contravariance is all about. It only works for interfaces though.

Generics and casting - cannot cast inherited class to base class

RepositoryBase<EntityBase> is not a base class of MyEntityRepository. You're looking for generic variance which exists in C# to a limited extent, but wouldn't apply here.

Suppose your RepositoryBase<T> class had a method like this:

void Add(T entity) { ... }

Now consider:

MyEntityRepository myEntityRepo = GetMyEntityRepo(); // whatever
RepositoryBase<EntityBase> baseRepo = (RepositoryBase<EntityBase>)myEntityRepo;
baseRepo.Add(new OtherEntity(...));

Now you've added a different kind of entity to a MyEntityRepository... and that can't be right.

Basically, generic variance is only safe in certain situations. In particular generic covariance (which is what you're describing here) is only safe when you only ever get values "out" of the API; generic contravariance (which works the other way round) is only safe when you only ever put values "into" the API (e.g. a general comparison which can compare any two shapes by area can be considered as a comparison of squares).

In C# 4 this is available for generic interfaces and generic delegates, not classes - and only with reference types. See MSDN for further information, read <plug>read C# in Depth, 2nd edition, chapter 13</plug> or Eric Lippert's blog series on the topic. Also, I gave a one hour talk about this at NDC in July 2010 - the video is available here.

Casting Generic base object to derived type

A reference declared as BaseSettings<T> may or may not refer to an actual instance of Settings1Manager. That's how inheritance works. In fact though, what you're doing is casting a base type reference that's actually referring to an object that you've just confirmed actually is of the derived type.

settings as Settings1Manager will compile, because as will just return null if the cast fails. You and I know that particular cast won't fail, but the compiler just follows its rules.

if (typeof(Settings1Manager).IsAssignableFrom(settings.GetType()))
{
return WriteSettings1ManagerToNetwork(settings as Settings1Manager);
}

This works too, because you can cast anything to object and you can cast object to anything. But you don't want people seeing your name on code that looks like this.

return WriteSettings1ManagerToFile((Settings1Manager)(object)settings);

Cast from base generic type to derived generic type where the derived type has additional type constraints

You could add a generic argument w/ constraints to your RestoreItem method

protected virtual async Task RestoreItem<T>(T item)
where T : TItem, IArchivable
{
if( orderableItems is not ArchivableOrderableCollection<T> items )
{
throw new InvalidOperationException();
}

// do what you need to here
items.Restore( item );
}

However, I think you should reconsider your design. Implement base and derived classes for OrderableListDisplay<T> so that a derived version may constrain the TItem to IArchivable properly and expects and implementation of ArchivableOrderableCollection<T>:

public class OrderableListDisplay<TItem>
where TItem : IOrderable, new()
{
private readonly OrderableCollection<TItem> _items = null;

public OrderableListDisplay( OrderableCollection<TItem> items )
{
_items = items ?? throw new ArgumentNullException( nameof( items ) );
}
}

public class ArchivableOrderableListDisplay<TItem>
: OrderableListDisplay<TItem>
where TItem : IArchivable
{
private readonly ArchivableOrderableCollection<TItem> _items = null;

public ArchivableOrderableListDisplay( ArchivableOrderableCollection<TItem> items )
: base( items )
{
_items = items;
}

protected async Task RestoreItem( TItem item )
{
_items.Restore( item );
}
}

Generic type is cast to base class

The type parameter T is inferred at compile time, that's why it's different when you call a method with a parameter Base b1 and Derived b2 (using var simply makes the type of the variable the same as the type of the expression used to initialize it).

The simplest way to make it happen at run time is to cast the parameter to dynamic

SomeMethod((dynamic)b1);

This will basically run the compiler at run time and perform overload resolution and type inference with run time types instead of compile time types.

In general what you're asking about is called double dispatch - choosing the method to call based on both the runtime type of the object whose method you call, and the run time type of the parameter. A traditional way to achieve it is to use the visitor pattern.

Casting generic class with inheritance

You cannot do that in C#. You can only do it by introducing a generic interface on top level which is co-variant on the generic parameter using out like:

public class Segment<K> : ISegment<K> where K : SomeStructure{...}

While your interface should look like:

public interface ISegment<out T>
{

}

and your code for casting will use interface type now :

data as ISegment<SomeStructure>

check this demo fiddle as example.

Why can't I cast a more derived generic type to a less derived generic type?

Consider we introduce messages and listeners for creating employees:

public class CreateMessage : IMessage {
public int EmployeeId { get; set; }
}

public class CreateEmployeeListener : IListener<CreateMessage> {
public void ProcessMessage(CreateMessage message) {
}
}

// Later...
var listener = (IListener<IMessage>) new DeleteEmployeeListener();
IMessage createMessage = new CreateMessage();
listener.ProcessMessage(createMessage); // What happens here?

Although createMessage is an IMessage, DeleteEmployeeListener can't handle a CreateMessage.

In the case of your Ilistener interface, the generic type is contravariant, meaning the "input" types are generic. This means you can pass a more specific type, but not less.

More info here: https://docs.microsoft.com/en-us/dotnet/standard/generics/covariance-and-contravariance



Related Topics



Leave a reply



Submit