一尘不染

在C#中可能进行部分泛型类型推断?

c#

我正在为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


阅读 346

收藏
2020-05-19

共1个答案

一尘不染

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

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

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

然后,您无需指定type参数,因此类型推断将在您提到的示例中起作用。请注意,您可以通过将委派给具有两个类型参数(问题中的一个)的单个通用扩展方法来实现这两种扩展方法。


通常,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);
}
2020-05-19