【问题标题】:c# generics covariance and contravariance conflictc#泛型协变和逆变冲突
【发布时间】:2017-04-21 11:23:43
【问题描述】:

我使用接口、继承和泛型用 c# 编写了以下代码:

public interface IBasic
{

}

public class Basic : IBasic
{

}

public class AnotherBasic : Basic
{

}

public interface IWorker<in TBasic>
{
    void Run(TBasic basic);
}

public class Worker : IWorker<Basic>
{
    public void Run(Basic basic)
    {
        throw new System.NotImplementedException();
    }
}

public class AnotherWorker : IWorker<AnotherBasic>
{
    public void Run(AnotherBasic basic)
    {
        throw new System.NotImplementedException();
    }
}

public void Test()
{
    List<IWorker<IBasic>> workers = new List<IWorker<IBasic>>
    {
        new Worker(),
        new AnotherWorker()
    };
}

这段代码的问题是worker和anotherworker类不适合IWorker&lt;IBasic&gt;的泛型列表,它们是worker和基本类的worker的父母。问题是 IWorker&lt;in TBasic&gt; 由于运行方法签名是逆变的,但是我需要它是协变的,以便填写 List&lt;IWorker&lt;IBasic&gt;&gt;。 run 方法必须具有 TBasic 参数,并且我需要此工作人员列表以用于责任链设计模式。我是否遗漏了什么,或者我是否找到了使协变和逆变不相互排斥的理由?

【问题讨论】:

  • 你不能这样做,因为它不安全,例如new List&lt;IWorker&lt;IBasic&gt;&gt;{new AnotherWorker()}[0].Run(new Basic()) 会编译但在运行时会失败。
  • 这看起来像XYProblem。如果您描述了您要解决的实际问题,那么您尝试使用此设计解决的任何问题都可以通过不同的设计来解决。
  • 我显然可以通过多种方法解决这个问题,但我希望它按照我展示的方式工作

标签: c# generics covariance contravariance


【解决方案1】:

你可以这样初始化它:

public void Test()
{
    List<IWorker<IBasic>> workers = new List<IWorker<IBasic>>
    {
        new Worker<IBasic>(),
        new AnotherWorker<IBasic>()
    };
    workers[0].Run(new Basic());
}

【讨论】:

  • 不,它不起作用,worker和另一个worker都不是通用的
【解决方案2】:

您的工人声明说“这是一个工人列表,每个工人都可以运行任何 IBasic 实现”,这是不正确的。

您可以尝试将工作人员可以处理的命令类型的责任转移到工作人员本身(事实上,这是chain of responsibility pattern 所建议的)。

public interface IWorker
{
    bool DidRun<TBasic>(TBasic basic);
}

public class WorkerChain
{
    private readonly List<IWorker> workers = new List<IWorker>
    {
        new Worker(),
        new AnotherWorker()
    };

    public bool DidRun<T>(T basic)
    {
        return workers.Any(worker => worker.DidRun(basic));
    }
}

public class Worker : IWorker
{
    public bool DidRun<T>(T basic)
    {
        if (!(basic is Basic))
        {
            return false;
        }

        Console.WriteLine($"running {basic}");
        return true;
    }
}

public class Test
{
    public void CanRunWorkBasic()
    {
        var didRun = new WorkerChain().DidRun(new Basic());
        Debug.Assert(didRun);
    }
}

【讨论】:

    【解决方案3】:

    如果您想将您的工作人员插入列表,您将需要一个非通用接口IWorker 并且IWorker&lt;TBasic&gt; 必须实现该接口。然后在List 中使用IWorker 而不是IWorker&lt;TBasic&gt;。现在将您的工人添加到List 没有更多问题。

    这样我们解决了一个问题,但不幸的是我们还创建了另一个问题,因为我们必须执行两次Run 方法。一次用于非通用接口,第二次用于通用接口。

    您可以使用抽象Worker 类来解决这个问题,默认情况下,当调用非泛型Run 时,会进行必要的检查,转换参数并将其传递给泛型Run 方法。然后您的工人可以从Worker 派生,每个人都可以有自己的角色。

    在下面的示例中,我试图展示我认为这段代码应该是什么样子。非泛型Run 方法已实现explicitly,为了安全起见,我还使用了generic type constraints。 Run 方法只是检查类型并进一步传递它。

    public interface IBasic
    {
    
    }
    
    public class Basic : IBasic
    {
    
    }
    
    public class AnotherBasic : Basic
    {
    
    }
    
    public interface IWorker
    {
        void Run(IBasic basic);
    }
    
    public interface IWorker<in TBasic> : IWorker where TBasic : IBasic
    {
        void Run(TBasic basic);
    }
    
    public abstract class Worker<TBasic> : IWorker<TBasic> where TBasic : IBasic
    {
        void IWorker.Run(IBasic basic)
        {
            if (basic is TBasic)
                Run((TBasic)basic);
        }
    
        public abstract void Run(TBasic basic);
    }
    
    public class FirstWorker : Worker<Basic>
    {
        public override void Run(Basic basic)
        {
            // ...
        }
    }
    
    public class SecondWorker : Worker<AnotherBasic>
    {
        public override void Run(AnotherBasic basic)
        {
            // ...
        }
    }
    
    
    public void Test()
    {
        List<IWorker> workers = new List<IWorker>
        {
            new FirstWorker(),
            new SecondWorker()
        };
    }
    

    【讨论】:

      【解决方案4】:

      因此,经过 2 天的学习和调查,我回答了自己的问题。代码如下:

      公共接口 IBasic {

      }
      
      public class Basic : IBasic
      {
      
      }
      
      public class AnotherBasic : Basic
      {
      
      }
      
      public interface IWorker<in TBasic>
      {
          void Run(TBasic basic);
      }
      
      public class SimpleWorker : IWorker<IBasic>
      {
          public void Run(IBasic basic)
          {
              throw new System.NotImplementedException();
          }
      }
      
      public class Worker : IWorker<Basic>
      {
          public void Run(Basic basic)
          {
              throw new System.NotImplementedException();
          }
      }
      
      public class AnotherWorker : IWorker<AnotherBasic>
      {
          public void Run(AnotherBasic basic)
          {
              throw new System.NotImplementedException();
          }
      }
      
      public class Final
      {
          public void Test()
          {
              List<IWorker<AnotherBasic>> workers = new List<IWorker<AnotherBasic>>
              {
                  new SimpleWorker(),
                  new Worker(),
                  new AnotherWorker()
              };
          }
      }
      

      TBasic in CONTRAVARIANT,意思是声明应该尽可能的具体,如代码所示:AnotherBasic 然后接受较少派生的类型,即父母,并编译代码。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-11-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-02-02
        相关资源
        最近更新 更多