编译时检查一个值以实现多个接口

时间:2012-04-27 13:50:17

标签: c# types interface

假设我想表达“一个期望实现接口IFoo和IBar 的对象的方法”。类似的东西:

void Method(IFoo+IBar param);

我怎么能用C#说这个?

(是否有一个语法结构?或者是一个好习惯?)


尝试

介绍一个界面:

interface IFooBar : IFoo, IBar {}

void Method(IFooBar param);

这很糟糕我很遗憾甚至想到它:-)乍一看看起来很好,但可悲的部分是默默地引入一个不应该在这里的依赖。

IFooBar存在的唯一原因是成为Method界面的一部分。因此,要用作Method参数的任何类对象必须知道此方法(和该接口)存在 - 否则不会出现这种情况。实现IFoo和IBar的所有类都需要修改以实现IFooBar(可能会知道一个新的程序集) - 这是非常不切实际的,如果我们不能修改它们,甚至是不可能的。

不必要的依赖=禁止。

解决方法

放弃静态输入:

void Method(object param)
{
    if (!param is IFoo)
        throw new ArgumentException("IFoo not supported", "param");
    if (!param is IBar)
        throw new ArgumentException("IBar not supported", "param");

    // ...
}

(或:在签名中定义一种类型并动态检查其他类型 - 如果一个是最重要的,则更可取,但更令人困惑)

工作正确,但在运行时(没有编译时检查)。迫切需要一个文档(每个人都要阅读它)。

也仅适用于功能参数。如果我试图在一个字段上使用它,那么代码会因为强制转换而大量膨胀。


这种情况的真实案例?例如IList<Foo> + INotifyCollectionChanged

4 个答案:

答案 0 :(得分:9)

public void someMethod<T>(T param) where T : IFoo, IBar
{...}

答案 1 :(得分:2)

我的一位朋友曾经问过Eric Lippert一个非常类似的问题,这是他的回答:

  

问:C#是否有理由不允许声明变量   多个接口?

     

答:假设我们将其注释为{IFoo,IBar}。

     

问题大于声明变量。基本上你是   说你要改变对“变量”的约束   变量必须是给定类型“to”变量必须全部   几种给定类型“。但为什么要停止变量?如果是那种类型   变量的约束然后应该是一个类型,句点。您   应该可以说:

List< { IFoo, IBar } > myList; Or
 public static { IFoo, IBar } MyMethod( { IFoo, IBar }[ ] foobars  )
 {  return foobars[0];  }
  

等等。中途完成这项功能毫无意义。

     

这将是类型系统的主要扩展。我们想要   支持每种托管语言,而不仅仅是C#和VB。这是   您希望在版本中进入框架的那种功能   一;这是非常昂贵的,很难做出如此重大的改变   当你有四个版本并拥有数十亿行客户时   代码必须继续工作。

     

我自己经常想要这个功能;我同意这很好   功能,但我认为现在添加它太昂贵了。下一个   你设计一个新类型系统的时间,包括该功能   开始!

答案 2 :(得分:2)

您可以通过定义下面的方法来实现它

void Method<T>(T param) 
    where T: IFoo, IBar
{
}

希望这有帮助。

答案 3 :(得分:1)

可以使用受约束的泛型参数来处理方法返回后不会持久化的事物。不幸的是,它们存在很大的局限性。一种方法如:

void foo(T param) where T:IFoo,IBar {...}
如果在编译时知道T某个特定类型,它实现IFooIBar,那么

很容易调用。遗憾的是,很难构造一个对象集合,这些对象可以传递给上面的例程,而不需要它们共享一个实现两个接口的公共基类型。如果想要尝试持有一个集合 “同时实现IFooIBar的东西,并且可以传递给期望相同的例程”,有办法实现,但它们并不容易。

如果可以事先确定一个人可能希望传递给对象的所有例程,则可以让一个集合为每个给定的对象创建封闭的通用委托,在给出一个对象时。在可行的情况下,这将起作用并且可能是合理有效的。但是,它确实在集合类和集合中对象的使用之间引入了非常强的依赖关系。

一种更通用的方法是让集合保持包装器对象,该对象支持一种方法,以对其内部对象执行任意约束 - 开放 - 通用操作。不幸的是,代表不能很好地工作,因为.net没有提供任何机制来调用开放的泛型委托而不首先使用Reflection将它们转换为封闭形式。相反,必须定义如下的接口:

interface IActUpon<T,U> {
  void DoSomething<MT>(ref MT it) where MT:T,U;
}
interface IActUpon<T,U,PT1> {
  void DoSomething<MT>(ref MT it, ref PT1 p1) where MT:T,U;
}
interface IActUpon<T,U,PT1,PT2> {
  void DoSomething<MT>(ref MT it, ref PT1 p1, ref PT2 p2) where MT:T,U;
}

如果对象支持方法族方法:

void DoSomethingWithMe(IActUpon<T,U> proc);
void DoSomethingWithMe(IActUpon<T,U,PT1> proc, ref PT1 p1);
void DoSomethingWithMe(IActUpon<T,U,PT1,PT2> proc, ref PT1 p1, ref PT2 p2);

通过创建一个实现IActUpon<T,U,...>的适当变体的简单类,可以让对象调用任何需要通过约束类型T和U的所需例程。

不幸的是,尽管这种方法几乎是一般性的(最大的限制是必须明确地为任何特定数量的通用参数编码)但它充其量只是尴尬。有了一些编译器的支持,它可能不会太糟糕,但是对于一般用途来说它可能太危险了。