简体   繁体   中英

Casting with generics

I'm running into an issue when trying to access an interface property on an implementing class. The problem is, I only have the specific (Cat) type at runtime, so my app breaks when it tries to cast.

Here's what I have:

public class Animal {}
public class Cat : Animal {}

public interface IPetSitter {}
public interface IPetSitter<T> IPetSitter where T : Animal {
    T Pet { get; set; }
}

public class Kid { }

public class NeighborhoodKid : Kid, IPetSitter<Animal> {
    Animal Pet { get; set; }
}

// --- Implementation ---

// Kid Timmy is instantiated elsewhere
// Animal type "A" is passed in dynamically

if (Timmy is IPetSitter) {
    ((IPetSitter<A>)Timmy).Pet = new A();
}

This cast will error if the types don't match. I'd love to do something like this:

public interface IPetSitter {
    object Pet { get; set; }
}

public interface IPetSitter<T> : IPetSitter where T : Animal {
    new T Pet { get; set; }
}

// --- Implementation ---

NeighborhoodKid Timmy = new NeighborhoodKid();
((IPetSitter)Timmy).Pet = new Cat();

But that forces anything implementing IPetSitter to have both [object Pet] and [Cat Pet] properties.

I'd appreciate any ideas. Thanks.

UPDATE: I should have made it more clear initially, but sometimes I will create a Kid class and sometimes a NeighborhoodKid class. That's why I need to cast to IPetSitter<T> . Not all kids I create will be sitting pets. This is starting to sound creepy.

Timmy, ultimately, was initialized as a NeighborhoodKid. Which means that Pet, for him, as Animal. Timmy is an IPetSitter<Animal> , and you can't cast it to IPetSitter<Cat> .

You could do it the other way around, though, assuming Cat : Animal.

This:

((IPetSitter<Animal>)Timmy).Pet = new Cat();

Actually works simply because Timmy really is IPetSitter<Animal> , since NeighborhoodKid : IPetSitter<Animal> , so you aren't really doing anything with that cast - just accessing the pet property.

The problem with the line after that, isn't accessing to Pet, nor putting Cat into it - it is casting Timmy to IPetSitter<Cat> that is the problem. You are downcasting it to something that it isn't.

You can always up-cast, but you can only down-cast into what you initialized the object with.

If you want NeighborhoodKid to be an IPetSitter of any sort of animal, including animal itself, you should do:

public class NeighborhoodKid<T> : IPetSitter<T> where T : Animal
{
    ...
}

That way, it is generic, and constraining it to being something that is either an Animal or something that derives from Animal, whether directly or indirectly.

Still, if you initialized it as new NeighborhoodKid<Animal>() , you will not be able to look at it as (aka cast it to) IPetSitter<Cat> , because it was INITIALIZED as an IPetSitter<Animal> (since the generic T parameter given to the NeighborhoodKid constructor was Animal, and is passed to the IPetSitter generic parameter).

The problem is that you defined

public class NeighborhoodKid : IPetSitter<Animal>
{
    Animal IPetSitter<Animal>.Pet { get; set; }
}

and not

public class NeighborhoodKid : IPetSitter<Cat>
{
    Cat IPetSitter<Animal>.Pet { get; set; }
}

or

public class NeighborhoodKid<T> : IPetSitter<T> where T : Animal
{
    Cat IPetSitter<T>.Pet { get; set; }
}

Why not just Timmy.Pet = new Cat(); ? Just make it public and youll be all set:

public class NeighborhoodKid : Kid, IPetSitter<Animal>
{
   public Animal Pet { get; set; }
}

If you create a NeighborhoodKid that doesnt inherit from IPetSitter , the setter wont be availabe.

public class LazyNeighborhoodKid : Kid
{
   // Nothing here, hes not a Pet Sitter, can access Pet
}

I'm not really a fan of generics outside of their usefulness in collections, and this is why. You are forcing every NeighborhoodKid to be bound to a single specific type of Animal. What if Timmy can watch cats or dogs? Are you going to create different Timmy instances for each?

Instead, I'm thinking you enforce animal types at at the instance level. For example (I've truncated some of the types for the sake of brevity):

public interface IAnimal {...}

public class Cat : IAnimal {...}

public interface IPetSitter
{
    IAnimal Pet { get; set; }
}

public class Kid : IPetSitter
{
    public Kid (params Type[] allowedPets) {
        _allowedPets = allowedPets;
    }

    readonly IEnumerable<Type> _allowedPets;

    IAnimal _pet;
    public IAnimal Pet 
    {
        get {
            return _pet;
        }
        set {
            if (!_allowedPets.Contains(value.GetType()) {
                throw new InvalidArgumentException("This instance does not support " + value.GetType().Name + ".");
            }
            _pet = value;
        }
    }
}

If you leave your enforcement at the instance level, then you don't necessarily need to use concrete casting just to set a property.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM