.NET泛型:如何在运行时解析类型T?

8
让我通过以下示例向您解释我正在解决的问题:
class Animal {}
class Cat: Animal {}
class Dog : Animal { }

interface IAnimalHandler<in T> where T: Animal
{
    void Handle(T animal);
}

class AnimalHandler : 
    IAnimalHandler<Cat>,
    IAnimalHandler<Dog>
{
    public void Handle(Cat animal)
    {
        Console.Write("it's a cat !");
    }

    public void Handle(Dog animal)
    {
        Console.Write("it's a dog !");
    }
}

现在我想遍历所有动物并运行适当的处理程序,就像这样:

  var ah = new AnimalHandler();
  var animals = new List<Animal> { new Cat(), new Dog() };
  animals.ForEach(a => ah.Handle(a));

然而,这段代码不起作用(无法解析方法Hanler<>...),因为.NET编译器在编译之前需要知道在此处使用了哪种类型,那么最好的解决方案是什么呢?换句话说,我需要在运行时为类型T的每个实例请求.NET编译器获取适当的处理程序。我不想使用多个if语句来检查实例类型。 更新:抱歉遗漏了这一点,对我来说似乎很明显,但现在我明白这并不是那么明显:AnimalHandler类包含的逻辑不应该成为Cat和Dog领域对象的一部分。将它们视为纯粹的领域对象,我不希望它们知道任何类型的处理程序。

4
你有没有任何原因不能或不想在你的具体CatDog类型中放置特定的行为? - FishBasketGordo
跟进@FishBasketGordo的话,你把功能放在AnimalHandler类中有什么原因吗?也就是说,如果Animal类有一个抽象属性“Name”,Cat和Dog类实现了它(也可以用接口实现),那么AnimalHandler就变得更简单了,只需要引用animal.Name或其他属性即可。 - JohnKeller
1
@FishBasketGordo:这只是我尝试实现的一个简化模型。不,猫和狗不能自己处理,因为它们是领域模型对象,而AnimalHandler包含业务逻辑、用户界面或其他类型的逻辑,不应该放在领域类中。 - YMC
7个回答

8
您可以使用C# 4中的dynamic将重载决定步骤从编译时移至运行时:
var ah = new AnimalHandler();
var animals = new List<Animal> { new Cat(), new Dog() };
animals.ForEach(a => ah.Handle((dynamic)a));

5

对我来说,听起来你可以从这个模式中受益(使用StructureMap实现)。根据你的原始陈述,“我需要请求.NET编译器在运行时为每个类型T的实例选择适当的处理程序”,它可能看起来像这样:

class Dog : Animal { }
class Cat : Animal { }

interface IHandler<T>
{
    void Handle(T eval);
}

class DogHandler : IHandler<Dog>
{
    public void Handle(Dog eval)
    {
        // do whatever
    }
}

class CatHandler : IHandler<Cat>
{
    public void Handle(Cat eval)
    {
        // do whatever
    }
}    

您可以按照链接文章中的说明配置 StructureMap,并使用以下方式获取适当的处理程序:
var dogHandler = _container.GetInstance<IHandler<Dog>>(); // instance of DogHandler
var catHandler = _container.GetInstance<IHandler<Cat>>(); // instance of CatHandler

更新: 要解决这些问题,您可以采用以下方法:

foreach (var animal in animals)
{
    var concreteHandlerType = typeof(IHandler<>).MakeGenericType(animal.GetType());
    var handler = _container.GetInstance(concreteHandlerType);
    handler.Handle(animal);
}

我在一个相当大的系统中使用这种模式来实现同样的目标(纯领域对象、用于不应该在这些领域对象内部的逻辑的处理程序,简化维护)。它在一个想要为每个对象拥有单独的处理程序类的系统中运行良好。

问题是如何在不使用IF语句的情况下,通过实例循环遍历动物集合来选择适当的处理程序。 - YMC
请查看我的编辑,仅基于类型信息,在循环中解决这些问题,而无需使用if语句。 - Shane Fulmer
谢谢,Shane。你的例子也很好,+1。然而我更喜欢Daniel的方法,因为代码更简洁,需要的更少的更改。 - YMC

1

使用反射完全一样的代码:

var ah = new AnimalHandler();
var animals = new List<Animal> { new Cat(), new Dog() };
animals.ForEach(a => {
  var method = ah.GetType().GetMethod("Handle", new Type[] {a.GetType()});
  method.Invoke(ah,new object[] { a });
});

它有效了,谢谢,+1。然而,我认为在这种情况下使用dynamic更好,就像Daniel所建议的那样。 - YMC

0
这里有一种方法:在Animal类中创建一个抽象方法,例如"BeingHandled()",然后所有继承自Animal的子类都必须提供自己的实现。
然后你的AnimalHandler类将有一个单独的Handle(Animal a)方法:
class AnimalHandler
{
    public void Handle(Animal a)
    {
        a.BeingHandled();
    }
}

无论你传递给Handle()哪种动物,都没有关系,因为任何继承自Animal的东西都必须有适当的实现才能工作,编译器会知道这一点,因为在Animal基类中有抽象方法声明。

0

由于您正在使用.NET 4.0,利用协变性/逆变性来为您的类型注入处理程序。

interface IAnimal
{
    string Name { get; set; }
}

class Dog : IAnimal
{
    public string Name { get; set; }
}

class Cat : IAnimal
{
    public string Name { get; set; }
}

interface IAnimalEvaluator<T>
{
    void Handle(IEnumerable<T> eval);
}

class AnimalHandler : IAnimalHandler<T> where T : IAnimal
{
    public void Handle(IEnumerable<T> eval)
    {
        foreach (var t in eval)
        {
            Console.WriteLine(t.Name);
        }
    }
}


List<Dog> dogs = new List<Dog>() { new Dog() { Name = "Bill Murray" } };
List<Cat> cats = new List<Cat>() { new Cat() { Name = "Walter Peck" } };

AnimalHandler <IAnimal> animalHandler = new AnimalHandler<IAnimal>();

animalEvaluator.Handle(dogs);
animalEvaluator.Handle(cats);

0
使用访问者模式和双重分派。它的工作原理是这样的。处理程序可以处理不同类型的动物。而不是让处理程序选择正确的方法,动物选择正确的方法。这很容易,因为动物总是需要相同的方法(“他”的方法)。
class Animal
{
    string Name { get; set; }
    abstract public Handle(IAnimalHandler handler);
}

class Cat : Animal
{ 
    public overrides Handle(IAnimalHandler handler)
    {
        handler.Handle(this); // Chooses the right overload at compile time!
    }
}

class Dog : Animal
{ 
    public overrides Handle(IAnimalHandler handler)
    {
        handler.Handle(this); // Chooses the right overload at compile time!
    }
}

interface IAnimalHandler
{
    void Handle(Cat cat);
    void Handle(Dog dog);
}

class AnimalHandler : IAnimalHandler
{
    public void Handle(Cat cat)
    {
        Console.Write("it's cat {0}", cat.Name);
    }

    public void Handle(Dog dog)
    {
        Console.Write("it's dog {0}", dog.Name);
    }
}

现在你可以像这样处理动物

IAnimalHandler handler = new AnimalHandler();
animals.ForEach(a => a.Handle(handler));

handler = new SomeOtherAnimalHandler();
animals.ForEach(a => a.Handle(handler));

1
不好意思没提到,我不希望Can和Dog意识到任何处理程序,将它们视为纯领域模型对象。 - YMC
请注意,动物不知道处理程序在做什么,它只是调用 handler.Handle(this);。您可以将域模型拆分为部分类,并将此调用放入单独的文件中。如果您自动生成域类(例如使用 T4),则此拆分将保留手动输入的代码。 - Olivier Jacot-Descombes
你也可以创建一个扩展方法,像这样在Animal上:public static void Handle(this Animal animal, IAnimalHandler handler),以便不在Animal类本身上定义此方法。 - annemartijn

0
为什么要为每种动物拥有特定的处理器。不如实现多个特定接口,只需实现>,并只有一个Handle(T obj)方法。如果需要特定类型的功能,则可以通过调用typeof(obj)来获取特定类型。

这是一种方法,但我的想法是摆脱多个检查类型的 if,每个方法只处理其类型的动物。 - YMC

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接