【问题标题】:.NET generics: how to resolve type T in run-time?.NET 泛型:如何在运行时解析类型 T?
【发布时间】:2012-07-17 17:13:23
【问题描述】:

让我在下面的示例中向您解释我要解决的问题:

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 编译器需要在编译之前知道这里使用的是哪种类型,所以什么可能是最好的这个问题的解决方案?换句话说,我需要让 .NET 编译器为每个类型 T 在运行时的实例采用适当的类型 T 处理程序。 我不想使用多个if 语句检查实例类型。

更新:很抱歉错过了它,这对我来说似乎很明显,但现在我明白它并不那么明显:AnimalHandler 类包含不应该是域对象 Cat 和 Dog 的一部分的逻辑。将它们视为纯粹的域对象,我不希望它们知道任何类型的处理程序

【问题讨论】:

  • 有什么理由不能或不想将特定行为放入具体的CatDog 类型中?
  • 要跟进@FishBasketGordo,您是否有理由将功能放入 AnimalHandler 类中?即,Animal 类有一个抽象属性NameCatDog 类实现(也可以通过接口完成),然后AnimalHandler 变得更简单,并且只引用animal.Name 或随便
  • @FishBasketGordo:这是我尝试实现的一个非常简化的模型。不,Cat 和 Dog 不能自己处理,因为它们是域模型对象,而 AnimalHandler 包含业务或 UI 或其他类型的逻辑,不应该在域类中。

标签: c# .net c#-4.0 generics


【解决方案1】:

您可以使用 C# 4 dynamic 将重载解决步骤从编译时移至运行时:

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

【讨论】:

    【解决方案2】:

    在我看来,您可以从this pattern(使用 StructureMap 实现)中受益。从您的原始陈述开始,“我需要让 .NET 编译器在运行时为 T 类型的每个实例采用适当的 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 的情况下通过实例循环遍历动物集合来选择合适的处理程序
    • 查看我的编辑以在循环中解决这些问题,而无需仅基于类型信息的 if 语句。
    • 谢谢你,谢恩。你的例子也有效,+1。不过我更喜欢 Daniel 的方法,因为代码更简洁,需要的更改更少
    【解决方案3】:

    正是您的代码,但使用了反射:

    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,正如丹尼尔所建议的那样,在这种情况下会好一些
    【解决方案4】:

    您为什么要为每种动物配备特定的处理程序。而不是实现多个特定接口,只需实现IAnimalHandler&lt;T&gt;,并且只有一个Handle(T obj) 方法。如果您随后需要特定类型的功能,您可以通过调用 typeof(obj) 来获取特定类型来处理它。

    【讨论】:

    • 这是一条路,但我的想法是摆脱多个ifs 检查类型,每个方法只处理其类型的动物
    【解决方案5】:

    这是一种方法:在 Animal 中创建一个抽象方法,例如称为“BeingHandled()”的东西,然后所有 Animal 的继承者都必须提供自己的实现。

    那么你的 AnimalHandler 类就会有一个 Handle(Animal a) 方法:

    class AnimalHandler
    {
        public void Handle(Animal a)
        {
            a.BeingHandled();
        }
    }
    

    传递给 Handle() 的动物并不重要,因为从 Animal 继承的任何东西 必须 具有正确的实现才能工作,编译器会知道这一点,因为你的抽象方法声明动物基类。

    【讨论】:

      【解决方案6】:

      由于您使用的是 .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);
      

      【讨论】:

        【解决方案7】:

        使用访问者模式和双重调度。它是这样工作的。处理程序可以处理不同类型的动物。动物选择了正确的方法,而不是让处理者选择正确的方法。这很容易,因为动物总是需要相同的方法(“his”方法)。

        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));
        

        【讨论】:

        • 抱歉没有提到它,我不希望 Can 和 Dog 知道任何处理程序,关于它们作为纯领域模型对象的事情
        • 请注意,动物不知道处理程序做什么,它只是调用handler.Handle(this);。您可以将域模型拆分为部分类并将此调用放在单独的文件中。如果您自动生成域类(例如使用 T4),则此拆分将保留手动输入的代码。
        • 您也可以像这样在 Animal 上创建一个扩展方法:public static void Handle(this Animal animal, IAnimalHandler handler) 以不在 Animal 类本身上定义此方法。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2019-04-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-14
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多