【问题标题】:Downcast instance using its type使用其类型的向下转型实例
【发布时间】:2012-02-02 16:32:22
【问题描述】:

我有一个使用泛型参数并需要实例的向下转换形式的方法。例如,

public abstract class Animal {
}
public class Dog : Animal {
}
public class Cat : Animal {
}
public class AnimalHandler {
    public virtual void Pet<T>(T animal)
    {
    }
}

给定一组动物。

public List<Animal> Animals { get; set; }

在调用 Pet 方法之前,如何让个别动物失望?

我目前正在以这种形式工作。

if (animal is Dog) {
    _animalHandler.Pet((Dog)animal);
}
if (animal is Cat) {
    _animalHandler.Pet((Cat)animal);
}

理想情况下,应该是这种形式。

var type = animal.GetType();
_animalHandler.Pet(animal.CastTo(type));

澄清:在将实例传递给方法之前,我需要实例是向下转换的类型。我需要一只狗或猫才能被传入。

【问题讨论】:

  • typeof 仅适用于我相信的类名或泛型。
  • 在 AnimalHandler 中,为什么将 Dog 视为 Dog 而不是 Animal 很重要?对象一条狗,但你为什么一定要知道它呢?这个 Pet 方法做了什么?请记住,该方法不能对 T 做出任何假设,除了约束可能揭示的内容。
  • 对于泛型方法来说,通常(并非总是)不好的做法是使用它的类型参数来做事。它使它...不那么通用...
  • @Anthony,因为我们使用 NHibernate 存储库的通用接口,而通用类型将确定查询运行的映射(和表)。
  • @AK_,我同意。然而,由于有限的表结构,我们必须在进行数据库调用之前进行向下转换,即使我们没有使用向下转换类型的任何属性。

标签: c# inheritance abstract-class


【解决方案1】:

您可以使用新的 .NET 4 dynamic 关键字完成此操作:

namespace Rextester
{
    public class Program
    {
    public abstract class Animal {
    }
    public class Dog : Animal {
    }
    public class Cat : Animal {
    }
    public class AnimalHandler {
        public virtual void Pet<T>(T animal) {
            Console.WriteLine(typeof(T));
        }
    }

    public class Problem {
        public List<Animal> Animals { get; set; }
        private readonly AnimalHandler _animalHandler;

        public Problem(AnimalHandler animalHandler) {
            _animalHandler = animalHandler;
            Animals = new List<Animal> { new Cat(), new Dog() };
        }

        public void MakeDecision() {
            foreach (var animal in Animals) {
                 _animalHandler.Pet(animal);
            }
        }
    }

        public static void Main(string[] args)
        {
            List<Animal> animals = new List<Animal>();
            animals.Add(new Dog());
            animals.Add(new Cat());

            var handler = new AnimalHandler();
            handler.Pet((dynamic)animals[0]);
            handler.Pet((dynamic)animals[1]);
        }
    }
}

请注意在对Pet 的调用中对dynamic 的转换。虽然这样做首先否定了Pet 是通用的必要性。

【讨论】:

  • 这会将我的收藏输入到 Animal 的一个特定实例中。以列表> 为例。
  • 老兄!它有效,这非常棒。你能更新你的答案吗?我会接受的。
  • @Ty。 - This 也可以使用,并且只需要对原始代码进行一行代码更改。 Console.WriteLine(typeof(T))Console.WriteLine(animal.GetType())。你的动物从一开始就不需要被铸造(这是上面所有人的观点)
  • 我需要 typeof(T) 是 Cat 或 Dog,实例总是正确的。
  • 好的,我将更新我的答案以反映使用动态关键字来获得您所追求的内容。
【解决方案2】:

如果,正如您的澄清所说,您“需要一个具体的 DogCat 来传递”,那么您的方法不能采用通用的 Animal

但是,我怀疑您缺少的是 Handler 不需要知道它拥有什么类型的动物,只需要知道如何处理该动物。请参阅此扩展示例:

public abstract class Animal {
    public abstract void Speak();
}
public class Dog : Animal {
    public override void Speak(){ Console.WriteLine("WOOF!! You're great."); }
}
public class Cat : Animal {
    public override void Speak(){ Console.WriteLine("Meow. Im better than you."); }
}
public class AnimalHandler {
    public virtual void Pet<T>(T animal) where T : Animal
    {
        animal.Speak();
    }
}

使用以下代码执行此操作时:

List<Animal> animals = new List<Animal>();
animals.Add(new Dog());
animals.Add(new Cat());

var handler = new AnimalHandler();
handler.Pet(animals[0]);
handler.Pet(animals[1]);

我相信您可以想象输出(如果没有,现场示例:http://rextester.com/UJZRB90548)。从这里你看,我只有一个BaseAnimal的列表,但是当它传入Handler时,调用了正确的方法。

【讨论】:

  • 这将调用正确的方法,但 AnimalHandler 仍然接收作为 Animal 类型的实例。不幸的是,泛型类型仍然是 Animal。
  • @Ty - 是的,这就是它的工作方式。如果您需要 Dog,则不能将其称为 Animal。如果你有一个动物列表,除了你已经知道的以外,你不能以任何方式将它投射到狗身上。
【解决方案3】:

你有几个选择。一种方法是为每种动物类型设置AnimalHandler 的子类,然后手动分派到正确的子类:

public class AnimalHandler
{
    public void Pet(Animal animal)
    {
        if (animal is Cat)
        {
            this.catHandler.Pet((Cat) animal);
        }
        else if (animal is Dog)
        {
            this.dogHandler.Pet((Dog) animal);
        }
        else
        {
            throw new ArgumentOutOfRangeException("animal");
        }
    }

    private readonly CatHandler catHandler;
    private readonly DogHandler dogHandgler;
}

作为所有 'if's 和 'is's 的替代方法,您可以将 AnimalType 枚举属性添加到 Animal 类并使用 switch 语句。

通过创建一个表示特定动物类型处理程序的接口,可以使上述方法更加通用和可扩展:

public enum AnimalType
{
    Cat,
    Dog,
}

public interface ISpecificAnimalHandler
{
    AnimalType AnimalType { get; }
    void Pet(Animal animal);
}

public class AnimalHandler
{
     public AnimalHandler()
     {
         var handlers = new ISpecificAnimalHandler[] { new CatHandler(), new DogHandler() };
         this.handlersByAnimal = handlers.ToDictionary(handler => handler.AnimalType, handler);
         // ...
     }

     public void Pet(Animal animal)
     {
         ISpecificAnimalHandler handler;
         if (!this.handlersByAnimalType.TryGet(animal.AnimalType, out handler))
         {
              throw new ArgumentOutOfRangeException("Animal of type " + animal.GetType().Name " not supported.");
         }

         handler.Pet(animal);
     }

     private IDictionary<AnimalType, ISpecificAnimalHandler> handlerByAnimalType;
}

public class CatHandler : ISpecificAnimalHandler
{
    public AnimalType AnimalType
    {
        get { return AnimalType.Cat;
    }

    public void Pet(Animal animal)
    {
        var cat = (Cat) animal;
        // do petting, meow
    }
}

另一个不同的选择是使用动态调度:

public class AnimalHandler
{
    public void Pet(Animal animal)
    {
        PetAnimal((dynamic) animal); // will dispatch to most appropriate method at runtime
    }

    public void PetAnimal(Cat cat)
    {
        // do petting, meow
    }

    public void PetAnimal(Dog dog)
    {
        // do petting, woof
    }
}

【讨论】:

    猜你喜欢
    • 2018-11-22
    • 1970-01-01
    • 2023-04-10
    • 2011-05-03
    • 1970-01-01
    • 2011-11-08
    • 1970-01-01
    • 2022-11-18
    • 1970-01-01
    相关资源
    最近更新 更多