简体   繁体   English

Select 实现多个协变接口的 class 中的协变接口类型

[英]Select the type of a covariant interface in a class that implement multiple covariant interfaces

We have a simple data provider interface with covariant parameter definition.我们有一个带有协变参数定义的简单数据提供者接口。

interface IDataProvider<out T>
{
    T Get();
}

The printer class will print the value of all IMessage.打印机 class 将打印所有 IMessage 的值。

class Printer
{
    private readonly IEnumerable<IDataProvider<IMessage>> DataProviders;

    public Printer(params IDataProvider<IMessage>[] dataProviders)
    {
        DataProviders = dataProviders;
    }

    public void Print()
    {
        foreach( var dataProvider in DataProviders)
        {
            Console.WriteLine( dataProvider.Get().Message );
        }
    }
}

interface IMessage
{
    string Message { get; }
}

If all class implement one IDataProvider, the behavior is as my expectation.如果所有 class 都实现一个 IDataProvider,则行为如我所料。 Following code will print "Hello" and "World".以下代码将打印“Hello”和“World”。

class Program
{
    static void Main(string[] args)
    {
        HelloProvider helloProvider = new HelloProvider();
        WorldProvider worldProvider = new WorldProvider();
        Printer printer = new Printer(helloProvider, worldProvider);
        printer.Print();
    }
}

class Hello : IMessage
{
    public string Message { get; } = "Hello";
}

class World : IMessage
{
    public string Message { get; } = "World";
}

class HelloProvider : IDataProvider<Hello>
{
    public Hello Get() => new Hello();
}

class WorldProvider : IDataProvider<World>
{
    public World Get() => new World();
}

However we have a bad boy implement two IDataProvider:但是我们有一个坏男孩实现了两个 IDataProvider:

class BadBoy : IDataProvider<Hello>, IDataProvider<World>
{
    // Assume that there are some shared state of Hello and World data.

    Hello IDataProvider<Hello>.Get() => new Hello();

    World IDataProvider<World>.Get() => new World();
}

I tried to cast BadBoy but Printer print two "Hello".我尝试投射 BadBoy,但打印机打印两个“Hello”。

BadBoy badBoy = new BadBoy();
Printer printer = new Printer( (IDataProvider<Hello>)badBoy, (IDataProvider<World>)badBoy );
printer.Print();

The best solution we have now is separate IDataProvider implementation:我们现在最好的解决方案是单独的 IDataProvider 实现:

class HelloProvider : IDataProvider<Hello>
{
    private readonly BadBoy BadBoy;

    public HelloProvider(BadBoy badBoy)
    {
        BadBoy = badBoy;
    }

    public Hello Get() => BadBoy.GetHello();
}

class WorldProvider : IDataProvider<World>
{
    private readonly BadBoy BadBoy;

    public WorldProvider(BadBoy badBoy)
    {
        BadBoy = badBoy;
    }

    public World Get() => BadBoy.GetWorld();
}

class BadBoy
{
    // Assume that there are some shared state of Hello and World data.

    public Hello GetHello() => new Hello();

    public World GetWorld() => new World();
}

I'm not satisfied with this solution because it may result in a lot of ceremonial codes as we continuously add new data type to our system.我对这个解决方案不满意,因为随着我们不断地向系统添加新的数据类型,它可能会导致大量的仪式代码。 Is there better way to tell Printer class which implementation of IDataProvider it should use?有没有更好的方法来告诉打印机 class 它应该使用哪个 IDataProvider 实现?


note:笔记:

This example is a simplified version of our real problem.这个例子是我们实际问题的简化版本。 In our project we resolve all IDataProvider from container and use reflection to create many calculation classes using those IDataProvider.在我们的项目中,我们从容器中解析所有 IDataProvider,并使用反射来使用这些 IDataProvider 创建许多计算类。 Please tell me if you need more detail of the real problem.如果您需要更多关于实际问题的详细信息,请告诉我。

You have painted yourself into a corner by duplicating a contract.你通过复制合同把自己画到了一个角落。 However (and without making you redsign the whole solution), one approach (albeit nasty) would be to reflect out the interfaces by using GetInterfaces and GetInterfaceMap then invoking the method you need, you could potentially cache the MethodInfo, if need be.但是(并且不让您重新设计整个解决方案),一种方法(尽管很讨厌)是通过使用GetInterfacesGetInterfaceMap然后调用您需要的方法来反映接口,如果需要,您可能会缓存 MethodInfo。

Returns an interface mapping for the specified interface type.返回指定接口类型的接口映射。

Note : This may or may not be helpful to you (depending on your circumstances) ¯\_(ツ)_/¯注意:这可能对您有帮助,也可能对您没有帮助(取决于您的情况) ¯\_(ツ)_/¯

class Printer
{

    public void Print()
    {
       foreach (var dataProvider in DataProviders)
       {
          var dataProviderType = dataProvider.GetType();

          foreach (var type in dataProviderType.GetInterfaces())
          {
             var methodInfo = dataProviderType
                .GetInterfaceMap(type)
                .TargetMethods
                .FirstOrDefault(x => x.Name.EndsWith("Get"));

             var invoke = (IMessage) methodInfo.Invoke(dataProvider, null);
             Console.WriteLine(invoke.Message);
          }
       }
    }

     ...

Usage用法

BadBoy badBoy = new BadBoy();
Printer printer = new Printer(badBoy);
printer.Print();

Output Output

Hello
World

Update更新

using a MethodInfo cache使用MethodInfo缓存

public List<MethodInfo> _cache;

public void Print()
{
   foreach (var dataProvider in DataProviders)
   {
      var dataProviderType = dataProvider.GetType();

      if (_cache == null)
      {
         _cache = dataProviderType.GetInterfaces().Select(x => dataProviderType.GetInterfaceMap(x)
            .TargetMethods
            .FirstOrDefault(x => x.Name.EndsWith("Get"))).ToList();
      }


      foreach (var item in _cache)
      {
         var invoke = (IMessage) item.Invoke(dataProvider, null);
         Console.WriteLine(invoke.Message);
      }

   }
}

On my pc, i can call the original 100,000 times in 400ms, with the cache, i can call it, 100,000 times in 60ms.在我的电脑上,我可以在 400 毫秒内调用原始的 100,000 次,使用缓存,我可以在 60 毫秒内调用它,100,000 次。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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