C#中可能的部分generics类型推断?

我正在为我的IoC类库重写我的流畅界面,当我重构一些代码以便通过基类共享一些常用function时,我遇到了麻烦。

注意 :这是我想要做的事情,而不是我必须做的事情。 如果我必须使用不同的语法,我会,但如果有人知道如何使我的代码以我想要的方式编译,那将是非常受欢迎的。

我希望某些扩展方法可用于特定的基类,并且这些方法应该是通用的,具有一个generics类型,与方法的参数相关,但是这些方法还应返回与特定后代相关的特定类型。被引用。

使用代码示例比上面的描述更好。

这是一个简单而完整的例子, 不起作用:

using System; namespace ConsoleApplication16 { public class ParameterizedRegistrationBase { } public class ConcreteTypeRegistration : ParameterizedRegistrationBase { public void SomethingConcrete() { } } public class DelegateRegistration : ParameterizedRegistrationBase { public void SomethingDelegated() { } } public static class Extensions { public static ParameterizedRegistrationBase Parameter( this ParameterizedRegistrationBase p, string name, T value) { return p; } } class Program { static void Main(string[] args) { ConcreteTypeRegistration ct = new ConcreteTypeRegistration(); ct .Parameter("age", 20) .SomethingConcrete(); // <-- this is not available DelegateRegistration del = new DelegateRegistration(); del .Parameter("age", 20) .SomethingDelegated(); // <-- neither is this } } } 

如果你编译它,你会得到:

 'ConsoleApplication16.ParameterizedRegistrationBase' does not contain a definition for 'SomethingConcrete' and no extension method 'SomethingConcrete'... 'ConsoleApplication16.ParameterizedRegistrationBase' does not contain a definition for 'SomethingDelegated' and no extension method 'SomethingDelegated'... 

我想要的是扩展方法( Parameter )能够在ConcreteTypeRegistrationDelegateRegistration上调用,并且在这两种情况下,返回类型应该与调用扩展的类型相匹配。

问题如下:

我想写:

 ct.Parameter("name", "Lasse") ^------^ notice only one generic argument 

而且Parameter返回一个与它被调用相同类型的对象,这意味着:

 ct.Parameter("name", "Lasse").SomethingConcrete(); ^ ^-------+-------^ | | +---------------------------------------------+ .SomethingConcrete comes from the object in "ct" which in this case is of type ConcreteTypeRegistration 

有什么方法可以欺骗编译器为我做这个飞跃吗?

如果我向Parameter方法添加两个generics类型参数,则类型推断会强制我提供both或none,这意味着:

 public static TReg Parameter( this TReg p, string name, T value) where TReg : ParameterizedRegistrationBase 

给我这个:

 Using the generic method 'ConsoleApplication16.Extensions.Parameter(TReg, string, T)' requires 2 type arguments Using the generic method 'ConsoleApplication16.Extensions.Parameter(TReg, string, T)' requires 2 type arguments 

这同样糟糕。

我可以很容易地重构类,甚至通过将方法引入层次结构来使方法成为非扩展方法,但我的问题是我是否可以避免必须复制两个后代的方法,并以某种方式仅将它们声明为一次,对于基类。

让我重新说一下。 有没有办法在上面的第一个代码示例中更改类,以便可以保留Main方法中的语法,而无需重复相关方法?

代码必须与C#3.0和4.0兼容。


编辑 :我不想将两个generics类型参数都留给推理的原因是,对于某些服务,我想为一种类型的构造函数参数指定一个参数值,但是传入一个作为后代的值。 目前,使用参数的名称和类型来匹配指定的参数值和正确的调用构造函数。

让我举个例子:

 ServiceContainerBuilder.Register(r => r .From(f => f.ConcreteType(ct => ct .Parameter("source", new FileStream(...))))); ^--+---^ ^---+----^ | | | +- has to be a descendant of Stream | +- has to match constructor of FileService 

如果我将两者都留给类型推断,则参数类型将是FileStream ,而不是Stream

如果您只有两种特定类型的注册(在您的问题中似乎就是这种情况),您可以简单地实现两种扩展方法:

 public static DelegateRegistration Parameter( this DelegateRegistration p, string name, T value); public static ConcreteTypeRegistration Parameter( this ConcreteTypeRegistration p, string name, T value); 

然后您不需要指定类型参数,因此类型推断将适用于您提到的示例。 请注意,您可以通过使用两个类型参数(问题中的一个)委派一个通用扩展方法来实现这两种扩展方法。


一般来说,C#不支持像o.Foo(..)这样的任何东西来推断第二个类型参数(这将是很好的function – F#有它,它非常有用:-))。 你可以实现一个允许你写这个的解决方法(基本上,通过将调用分成两个方法调用,得到两个可以应用类型推理的地方):

 FooTrick().Apply(); // where Apply is a generic method 

这是一个伪代码来演示结构:

 // in the original object FooImmediateWrapper FooTrick() { return new FooImmediateWrapper { InvokeOn = this; } } // in the FooImmediateWrapper class (...) Apply(arguments) { this.InvokeOn.Foo(arguments); } 

我想创建一个可以枚举事物列表的扩展方法,并返回某些类型的事物列表。 它看起来像这样:

 listOfFruits.ThatAre().Where(banana => banana.Peel != Color.Black) ... 

可悲的是,这是不可能的。 此扩展方法的建议签名看起来像:

 public static IEnumerable ThatAre (this IEnumerable source) where TResult : TSource 

…并且对ThatAre <>的调用失败,因为需要指定两个类型参数,即使可以从使用中推断出TSource。

根据其他答案中的建议,我创建了两个函数:一个捕获源,另一个允许调用者表达结果:

 public static ThatAreWrapper That (this IEnumerable source) { return new ThatAreWrapper(source); } public class ThatAreWrapper { private readonly IEnumerable SourceCollection; public ThatAreWrapper(IEnumerable source) { SourceCollection = source; } public IEnumerable Are() where TResult : TSource { foreach (var sourceItem in SourceCollection) if (sourceItem is TResult) yield return (TResult)sourceItem; } } } 

这导致以下调用代码:

 listOfFruits.That().Are().Where(banana => banana.Peel != Color.Black) ... 

……这不错。

请注意,由于generics类型约束,以下代码:

 listOfFruits.That().Are().Where(truck => truck.Horn.IsBroken) ... 

将无法在Are()步骤编译,因为Trucks不是Fruit。 这胜过提供的.OfType <>函数:

 listOfFruits.OfType().Where(truck => truck.Horn.IsBroken) ... 

这编译,但总是产生零结果,并且尝试没有任何意义。 让编译器帮助你发现这些东西要好得多。

为什么不指定零类型参数? 两者都可以在您的样本中推断出来。 如果这对你来说不是一个可接受的解决方案,我也经常遇到这个问题并且没有简单的方法来解决问题“仅推断一个类型参数”。 所以我会选择重复的方法。

以下内容如何:

使用您提供的定义: public static TReg Parameter( this TReg p, string name, T value) where TReg : ParameterizedRegistrationBase

然后转换参数,以便推理引擎获得正确的类型:

 ServiceContainerBuilder.Register(r => r .From(f => f.ConcreteType(ct => ct .Parameter("source", (Stream)new FileStream(...))))); 

我认为你需要在两个不同的表达式之间拆分两个类型参数; 使显式的一个成为扩展方法的参数类型的一部分,因此推理可以接受它。

假设您声明了一个包装类:

 public class TypedValue { public TypedValue(TValue value) { Value = value; } public TValue Value { get; private set; } } 

然后你的扩展方法为:

 public static class Extensions { public static TReg Parameter( this TReg p, string name, TypedValue value) where TReg : ParameterizedRegistrationBase { // can get at value.Value return p; } } 

再加上一个更简单的过载(以上可能实际上称之为):

 public static class Extensions { public static TReg Parameter( this TReg p, string name, TValue value) where TReg : ParameterizedRegistrationBase { return p; } } 

现在在您很乐意推断参数值类型的简单情况下:

 ct.Parameter("name", "Lasse") 

但是在需要明确说明类型的情况下,您可以这样做:

 ct.Parameter("list", new TypedValue>(new List())) 

看起来很丑,但希望比简单的完全推断类型更罕见。

请注意,您可以只使用无包装器重载并写入:

 ct.Parameter("list", (IEnumerable)(new List())) 

但是,如果你出错了,那当然有缺点是在运行时失败。 不幸的是,现在远离我的C#编译器,所以如果这样的话就道歉了。