c# - C# 中是否可能进行部分泛型类型推断?

标签 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> ) 能够在 ConcreteTypeRegistration 上被调用和 DelegateRegistration ,并且在这两种情况下,返回类型都应与调用扩展的类型相匹配。

问题如下:

我想写:

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 .

最佳答案

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

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

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

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

...并且对 ThatAre<> 的调用失败,因为需要指定两个类型参数,即使可以从用法中推断出 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() 步骤编译,因为卡车不是水果。这击败了提供的 .OfType<> 函数:

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

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

关于c# - C# 中是否可能进行部分泛型类型推断?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16478772/

相关文章:

c# - AvalonEdit:确定您是否在评论中

c# - 为什么列表框模板中的绑定(bind)命令无法运行?

java - 如何调用泛型方法?

c++ - 带字符串文字的自动

scala - "val a:A = new B ",有什么意义?

c# - 了解 MongoDb 连接字符串

c# - MSTest:单元测试 - 未找到输入文件 ... vsmdi

Java arraylist 找不到构造函数,使用 arrays.aslist

使用泛型和类型推断的 C# 方法解析

typescript - 预期有 3 个类型参数但得到 1 个但它应该推断出 2 种类型