推断具有两个泛型类型参数的泛型类型

时间:2018-01-18 13:02:43

标签: c# generics type-inference

我有以下方法

public bool HasTypeAttribute<TAttribute, TType>(TType obj)
{
    return typeof(TType).GetCustomAttribute<TAttribute>() != null;
}

我希望能够像这样使用它:

MyClass instance = new MyClass();

TypeHelper.HasTypeAttribute<SerializableAttribute>(instance);

但由于

,我无法正常工作
  

类型参数数量不正确

所以我需要打电话

TypeHelper.HasTypeAttribute<SerializableAttribute, MyClass>(instance);

这当然有道理,但为什么编译器不能推断传递对象的类型?因为如果方法看起来像这样:

public void Demo<T>(T obj)
{
}

编译器肯定能够推断出类型,以便我可以编写

Foo.Demo(new Bar());

而不是

Foo.Demo<Bar>(new Bar());

那么,在这种情况下,有没有办法让类型推断工作?这是我的设计缺陷还是我可以实现我想要的?重新排序参数也无济于事......

2 个答案:

答案 0 :(得分:4)

因为C#规则不允许这样做。

C#有一个规则,如果某些类型与参数相关(因此至少在某些时候可以推断),并且显式给定类型的数量与剩余的数量相同不可推断的类型,然后两者将协同工作。

这需要有人提出它,说服参与C#决策的其他人这是一个好主意,然后才能实施。这没有发生。

除了功能开始必须证明自己值得他们带来的额外复杂性这一事实(在语言中添加任何东西,并且随着更多工作,编译器错误的机会更多等等,它立即变得更复杂),问题是,这是一个好主意吗?

在加号上,此特定示例中的代码会更好。

从根本上讲,每个人的代码现在都稍微复杂一些,因为错误的方法可能会出错,导致代码在运行时失败而不是编译时或更少有用的错误消息。

人们已经发现一些关于推理的案例令人困惑,这表明添加另一个复杂案例的想法没有帮助。

这并不是说这绝对是一个坏主意,只是有利有弊使其成为一个意见问题,而不是明显缺乏。

答案 1 :(得分:0)

可以将呼叫分为多个步骤,从而可以在任何可能的地方键入推理。

public class TypeHelperFor<TType>
{
    public bool HasTypeAttribute<TAttribute>() where TAttribute : Attribute
    {
        return typeof(TType).GetCustomAttribute<TAttribute>() != null;
    }
}

public static class TypeHelper
{
    public static TypeHelperFor<T> For<T>(this T obj)
    {
        return new TypeHelperFor<T>();
    }
}

// The ideal, but unsupported
TypeHelper.HasTypeAttribute<SerializableAttribute>(instance);
// Chained
TypeHelper.For(instance).HasTypeAttribute<SerializableAttribute>();
// Straight-forward/non-chained
TypeHelper.HasTypeAttribute<SerializableAttribute, MyClass>(instance);

在这种情况下应该可以正常工作,但是在最终方法返回void的情况下,我警告您不要使用它,因为如果您不对返回值做任何事情,很容易将链的一半保留

例如

// If I forget to complete the chain here...
if (TypeHelper.For(instance)) // Compiler error

// But if I forget the last call on a chain focused on side-effects, like this one:
// DbHelper.For(table).Execute<MyDbOperationType>();
DbHelper.For(table); // Blissfully compiles but does nothing

// Whereas the non-chained version would protect me
DbHelper.Execute<MyTableType, MyDbOperationType>(table);