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

时间:2010-05-23 22:11:56

标签: c# generics type-inference fluent-interface

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

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

我希望某些扩展方法可用于特定的基类,并且这些方法应该是通用的,有一个泛型类型,与方法的参数相关,但是这些方法也应该返回与之相关的特定类型。特别的后裔,他们被援引。

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

以下是无法工作的简单而完整的示例:

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<T>(
            this ParameterizedRegistrationBase p, string name, T value)
        {
            return p;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            ConcreteTypeRegistration ct = new ConcreteTypeRegistration();
            ct
                .Parameter<int>("age", 20)
                .SomethingConcrete(); // <-- this is not available

            DelegateRegistration del = new DelegateRegistration();
            del
                .Parameter<int>("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<T>)能够在ConcreteTypeRegistrationDelegateRegistration上调用,并且在这两种情况下,返回类型应该与类型匹配在。上调用了扩展名。

问题如下:

我想写:

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

Parameter<T>返回与其调用的类型相同的对象,这意味着:

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

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

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

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

给了我这个:

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

这同样糟糕。

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

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

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


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

让我举个例子:

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

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

5 个答案:

答案 0 :(得分:13)

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

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

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

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

...以及对ThatAre&lt;&gt;的调用失败是因为需要指定两个类型参数,即使可以从使用中推断出TSource。

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

public static ThatAreWrapper<TSource> That<TSource>
    (this IEnumerable<TSource> source)
{
    return new ThatAreWrapper<TSource>(source);
}

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

这导致以下调用代码:

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

......这不错。

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

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

将无法在Are()步骤中编译,因为Trucks不是Fruit。这胜过提供的.OfType&lt;&gt;功能:

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

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

答案 1 :(得分:12)

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

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

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

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


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

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

这是一个用于演示结构的伪代码:

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

答案 2 :(得分:2)

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

答案 3 :(得分:0)

以下内容如何:

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

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

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

答案 4 :(得分:0)

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

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

public class TypedValue<TValue>
{
    public TypedValue(TValue value)
    {
        Value = value;
    }

    public TValue Value { get; private set; }
}

然后你的扩展方法为:

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

另外一个更简单的重载(上面可能实际上称之为):

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

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

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

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

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

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

请注意,您可以只有no-wrapper重载并写:

ct.Parameter("list", (IEnumerable<int>)(new List<int>()))

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