泛型扩展方法中的额外泛型参数?

8
我想为泛型类A编写一个扩展方法,该方法接受另一个泛型类型(在此示例中为TC),但我猜这不可能?
class Program
{
    static void Main(string[] args)
    {
        var a = new A<B, B>();
        a.DoIt<B>();
    }
}

static class Ext
{
    public static A<TA, TB> DoIt<TA, TB, TC>(this A<TA, TB> a)
    {
        return a;
    }
}

class A<TA, TB> { }
class B { }

3
一个有趣的想法,但我们不支持。本质上,这将是对通用方法的通用类型参数进行“部分求值”;通常人们会考虑到在方法的形式参数上进行currying和部分求值。当然有一些语言使得形式参数的部分求值变得容易,但是否有任何人知道是否有语言(函数式或其他)允许对通用类型参数进行部分求值?那将非常棒。 - Eric Lippert
3
这就是 F# 扩展方法的工作方式。此外,F#允许您对不想指定的类型参数使用“_”,并且可以进行推断,因此即使在非扩展方法调用中,您也可以使用Foo<_,_,TypeICareAbout>(...)代替Foo<LongTypeName, AnotherInferableType, TypeICareAbout>(...),这通常可以提高可读性。另一方面,F#不允许在封闭泛型类型上使用扩展成员,这是 C# 中非常好的功能。 - kvb
@kvb:很酷,感谢提醒。自1993年以来,我实际上没有编写过任何ML变体的程序,所以我有点生疏。我真的应该编写一些F#程序,看看它是如何工作的。 - Eric Lippert
3个回答

5
如果您能接受轻微的语法更改,那么这是可能的。 将其更改为:
var a = new A<B, B>(); 
a.Do().It<B>(); 

诀窍在于Do方法是在A<TA, TB>上的一个扩展方法:
public static Doer<TA, TB> Do<TA, TB>(this A<TA, TB> a)
{
    return new Doer<TA, TB>(a);
}

这个技巧的关键在于,这个签名让类型推断从a中获取TA和TB,因此您不必显式地指定它们。
Doer类提供了您需要的通用方法:
public class Doer<TA, TB>
{
    public void It<TC>() { }
}

3

不是不可能,但你必须给编译器提供一些关于“TC”是什么的可接受上下文。第三个参数TC在你的代码中没有其他用途,因此它可以是任何东西,因此编译器会报错。然而,如果你给扩展方法添加一个类型为TC的入参,就可以实现编译器推断出TC的实际类型的情况,然后当你调用该方法时甚至不需要指示类型:

class Program
{
    static void Main(string[] args)
    {
        var a = new A<B, B>();
        string tc = "Hi!";
        a.DoIt(tc);
    }
}

static class Ext
{
    public static A<TA, TB> DoIt<TA, TB, TC>(this A<TA, TB> a, TC c)
    {
        return a;
    }
}

class A<TA, TB> { }
class B { }

但是,你需要给编译器提供一些上下文。

话虽如此,指定通用参数是一个全有或全无的努力。要么编译器可以推断出每个通用类型参数的类型,要么它无法推断,你就必须告诉它所有这些参数的类型。


@David Morton:你想说的是“类型推断”,这个术语用来描述这个特性。 - casperOne
@casperOne 我想我比你先完成了。我在发帖后立即编辑了原始帖子以进行一般通信和“术语”修改。 - David Morton
其实,我只需要类型而不是实例 =/ a.DoIt(typeof(B)) 不如 a.DoIt<B>() 看起来好看,这就是全部,没什么大不了的。谢谢! - Carl Hörberg

1

你说得对,这是不可能的。你必须要么指定所有类型参数(TATBTC),要么不指定任何一个(让编译器的类型推断来处理)。

有几种可能性:

  • DoIt 转换为实例方法(虽然我猜你故意将其作为扩展方法)
  • DoIt 添加另一个参数,以某种方式约束 TC,这意味着类型推断将起作用

关于第二种情况的示例,请看 Enumerable.Select:它有两个类型参数,用于源类型和目标类型,但它们都是从传递给 Select 的参数中推断出来的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接