【问题标题】:How can I express that an argument needs to implement multiple interfaces in C#如何表达一个参数需要在 C# 中实现多个接口
【发布时间】:2012-10-25 13:11:53
【问题描述】:

类似于这个问题:How can I require a method argument to implement multiple interfaces? 我想要一个方法参数来实现多个接口。

接口应该可以任意组合,我不想为每个有效的组合创建一个接口。

想想一个文件。可以是:

  1. 可读 => IReadable
  2. 可写 => IWriteable
  3. 存档 => IArchive
  4. 自动生成 => IGenerated

...

如果我想表达一个参数需要是一个可写的生成存档,我不想生成IWritableGeneratedArchive,因为组合太多,我想将它与一些我无法修改的现有类一起使用。

伪代码:

void WriteTo( IWritable + IGenerated + IArchive file)
{
   //...
}

【问题讨论】:

    标签: c# interface interface-design


    【解决方案1】:

    我在这里找到的解决方案:How can I require a method argument to implement multiple interfaces? 针对 C# 进行了调整。

    学分转到Michael Myers

    internal interface IF1
    {
        void M1();
    }
    
    internal interface IF2
    {
        void M2();
    }
    
    internal class ClassImplementingIF1IF2 : IF1, IF2
    {
    
        public void M1()
        {
            throw new NotImplementedException();
        }
    
        public void M2()
        {
            throw new NotImplementedException();
        }
    
    }
    
    internal static class Test
    {
    
        public static void doIT<T>(T t) where T:IF1,IF2
        {
            t.M1();
            t.M2();
        }
    
        public static void test()
        {
            var c = new ClassImplementingIF1IF2();
            doIT(c);
        }
    }
    

    【讨论】:

      【解决方案2】:

      可能是一个通用的 + 约束?

      void WriteTo<T>( T file) where T : IWritable,IGenerated,IArchive
      {
         //...
      }
      

      【讨论】:

      • 这个解决方案几乎是常识;-)
      【解决方案3】:

      使用通用约束:

      void WriteTo<T>(T file) where T: IWritable, IGenerated, IArchive
      {
         //...
      }
      

      Constraints on Type Parameters (C# Programming Guide)

      【讨论】:

      • 在编译时强制执行约束时,这种方法是必要的。然而,重要的是要注意,如果一个人有一个未知类型的对象,该对象预计会满足约束,但不知道任何单一类型可以满足所有约束,那么调用例程将很困难,并且对象将从中派生。此外,很难将此类类型的对象存储在字段中,以便稍后将它们传递给此类方法。
      • @supercat 说得好!我想不出现在存储这些对象的好方法......
      • @Onur:有很多方法可以做到,但很难。我想出了一个没有反射的解决方案,但它很笨重。使用反射来动态调度事情会相当容易,但速度很慢。一个主要的困难是,在许多情况下,人们希望传递指向未绑定的泛型函数的委托(例如,传递一个可用于调用 ActOnThing&lt;T&gt;(T param) where T:constraints 的委托,而委托的供应商不必知道 T),但是无法创建和调用此类委托。
      • @Onur: 可以做的是用ActOnThing&lt;TThing, Constraint1, Constraint2&gt; where TThing:Constraint1,Constraint2 方法定义一个类似IActOnConstrained&lt;Constraint1,Constraint2&gt; 的接口。可以像前面提到的开放委托一样传递IActOnConstrainted&lt;...&gt; 的实例,并且接收者可以在执行回调时替换适当的类型,但是在创建接口实现时,没有可用于创建内联委托的语法糖。
      • @supercat:我认为您的IActOnConstrained 类似于我下面提到的Holder 方法。
      【解决方案4】:

      我不想生成 IWritableGeneratedArchive,因为组合太多

      怎么组合太多了?无论有多少潜在组合,您只需要为实际用作方法参数的组合创建一个组合接口,然后无论如何都要写出来。换句话说,实际上并没有那么多工作。

      我想将它与一些我无法修改的现有类一起使用。

      可以将 IWritableGeneratedArchive 类型传递给只需要 IArchive 或 IWritable 的对象。这仍然适用于您已有的任何其他东西。


      但既然您已经决定不这样做,而且看起来您已经在这个架构上投入了大量资金,我想知道您是否见过 Code Contracts。它们看起来可以满足您的需求。

      【讨论】:

      • 问题是,如果一个已经存在且不可修改的类实现了IFooIBar,但没有实现任何继承这两者的接口,那么就没有办法定义一个既实现又实现的接口该课程将满足这一要求。更改类中的一行并重新编译就足够了,但这并不总是可行的。
      • @supercat 这正是我想要一个解决方案的原因。另一点是没有kust 2接口的组合爆炸。如果您想要 n 个接口的所有组合,则必须添加 2^n-1-n 个接口!这意味着 5 个现有接口的 26 个额外接口!
      • @Joel 我会仔细看看代码合约。对于这个问题,他们似乎有点矫枉过正。
      • @Onur:实际上,只有一小部分组合真正有用。如果存在需要这三个接口的代码,并且需要接受没有其他公共接口的类的实例,那么定义一个组合 I1、I2 和 I3 的接口才有用。在许多情况下,这种情况可以使用“可能”接口来处理(即,接口具有在某些实例上可用但在其他实例上不可用的成员,以及说明哪些成员在给定实例上可用的属性)。我真的希望接口可以指定默认实现,因为...
      • ...实现IBasicFoo [并保证其中的所有方法都有效] 和IMaybeComplicatedFoo [包括IBasicFoo 以及一些不受支持的接口] 的类必须为所有它包含但不支持的接口方法。
      【解决方案5】:

      此解决方案试图将 supercat 的想法与存储此类“多界面”对象结合起来。

      我通过将它们存储在一个Holder 类中来实现这一点,该类公开对象的声明接口。

      Holder 对象可以被存储并且是预期的参数。缺点是您必须首先创建Holder,并且Holder 的类型参数的顺序很重要。

      从好的方面来说,您也可以动态创建Holders,当然还可以存储“多接口”对象。

      启用holder.Get&lt;T&gt;() 方法(比 holder.t1 更好)会很好,但它不会编译。 也许有人知道如何解决它?我认为它需要添加约束 T1 is not T2 ,反之亦然。此接缝与C# generic does *not* implement something 相关,但他们没有找到解决方案。

      internal interface IF1
      {
          void M1();
      }
      
      internal interface IF1_extension : IF1
      {
      }
      
      internal interface IF2
      {
          void M2();
      }
      
      internal class ClassImplementingIF1IF2 : IF1_extension, IF2
      {
      
          public void M1()
          {
              throw new NotImplementedException();
          }
      
          public void M2()
          {
              throw new NotImplementedException();
          }
      
      }
      
      internal interface Getter<T> where T : class
      {
          T Get();
      }
      
      internal class Holder<T1, T2> //: Getter<T1>, Getter<T2> // not possible since T1 and T2 may be the same => won't compile!
          where T1 : class
          where T2 : class
      {
          private Holder(T1 t1, T2 t2)
          {
              Debug.Assert(t1 != null, "Argument is no " + typeof(T1).Name);
              Debug.Assert(t2 != null, "Argument is no " + typeof(T2).Name);
              this.t1 = t1;
              this.t2 = t2;
          }
      
          public static Holder<T1, T2> CreateFrom<T>(T t) where T : T1, T2
          {
              return new Holder<T1, T2>(t, t);
          }
          public static Holder<T1, T2> CreateDynamicallyFrom(object t)
          {
              return new Holder<T1, T2>(t as T1, t as T2);
          }
      
          public readonly T1 t1;
          public readonly T2 t2;
      
          //T1 Getter<T1>.Get()
          //{
          //    return t1;
          //}
          //T2 Getter<T2>.Get()
          //{
          //    return t2;
          //}
      }
      
      internal class Holder<T1, T2, T3>  // Holder<T1,T2,T3,T4> etc. are defined in a similar way
          where T1 : class
          where T2 : class
          where T3 : class
      {
          private Holder(T1 t1, T2 t2, T3 t3)
          {
              Debug.Assert(t1 != null, "Argument is no " + typeof(T1).Name);
              Debug.Assert(t2 != null, "Argument is no " + typeof(T2).Name);
              Debug.Assert(t3 != null, "Argument is no " + typeof(T3).Name);
              this.t1 = t1;
              this.t2 = t2;
              this.t3 = t3;
          }
      
          public static Holder<T1, T2,T3> CreateFrom<T>(T t) where T : T1, T2, T3
          {
              return new Holder<T1, T2, T3>(t, t, t);
          }
          public static Holder<T1, T2, T3> CreateDynamicallyFrom(object t)
          {
              return new Holder<T1, T2, T3>(t as T1, t as T2, t as T3);
          }
      
          public readonly T1 t1;
          public readonly T2 t2;
          public readonly T3 t3;
      
      }
      
      
      internal static class Test
      {
      
          public static void doIt<T>(T t) where T : IF1, IF2
          {
              t.M1();
              t.M2();
          }
      
          public static void doIt(Holder<IF1, IF2> t) // Interfaces should be mentioned in alpahbetical order since Holder<IF1,IF2> != Holder<IF2,IF1>
          {
              t.t1.M1();
              t.t2.M2();
          }
      
      
          public static void doIt_extended<T1, T2>(Holder<T1, T2> t) // handles conversions from Holder<T1,T2> to Holder<T1 or base of T1, T2 or base of T2>
              where T1 : class, IF1
              where T2 : class, IF2
          {
              t.t1.M1();
              t.t2.M2();
          }
      
          public static void test()
          {
              var c = new ClassImplementingIF1IF2();
              doIt(c);
              var c_holder = Holder<IF1, IF2>.CreateFrom(c);
              doIt(c_holder);
      
              var another_c_holder = Holder<IF1_extension, IF2>.CreateFrom(c);
              doIt_extended(another_c_holder);
      
      
              object diguised_c = c;
              var disguised_c_holder = Holder<IF1, IF2>.CreateDynamicallyFrom(diguised_c);
              doIt(disguised_c_holder);
      
          }
      }
      

      【讨论】:

      • 您需要有一个抽象的HolderBase&lt;T1,T2&gt; 和一个具体的Holder&lt;T,T1,T2&gt; : HolderBase&lt;T1,T2&gt; where T:T1,T2,它将对象保存在it 类型的T 字段中。然后,Holder&lt;T,T1,T2&gt; 将能够将该字段传递给采用受限于T1T2 的泛型类型参数的方法。如果有多个HolderBase&lt;T1,T2&gt; 引用并希望将它们传递给Foo&lt;T&gt;(T param) where T:T1,T2,则可以在HolderBase&lt;T1,T2&gt; 中包含一个抽象方法InvokeFoo。然后,在Holder&lt;T,T1,T2&gt; 中,覆盖该方法...
      • ...打电话给Foo(it)。如果可以在HolderBase&lt;T1,T2&gt; 中列出需要调用的所有方法,那可能是最简单的方法。否则,可以使用接口来描述想要调用的方法,但这有点笨拙。
      • 这种方法看起来很有趣。特别是因为“用户”可以实现Holder 类并添加他需要的方法。这使库程序员免于猜测可能需要什么。
      • Holder&lt;T,T1,T2&gt;HolderBase&lt;T1,T2&gt; 可能会在同一个源文件中连续声明,后者只需添加一个T 类型的字段并覆盖抽象方法来对其进行操作。如果您想允许调用 HolderBase&lt;T1,T2&gt; 中未明确提供的方法,则有一些方法可以做到,但它们很丑。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-01-18
      • 1970-01-01
      • 1970-01-01
      • 2016-09-09
      • 2011-03-03
      • 2020-07-08
      • 1970-01-01
      相关资源
      最近更新 更多