C#通用接口专业化

我想知道在C#中是否以某种方式专门化通用接口方法? 我发现了类似的问题,但没有完全像这样。 现在我怀疑答案是“不,你不能”,但我想确认一下。

我所拥有的是以下内容。

public interface IStorage { void Store(T data); } public class Storage : IStorage { public void Store(T data) { Console.WriteLine("Generic"); } public void Store(int data) { Console.WriteLine("Specific"); } } class Program { static void Main(string[] args) { IStorage i = new Storage(); i.Store("somestring"); // Prints Generic i.Store(1); // Prints Generic Storage s = (Storage)i; s.Store("somestring"); // Prints Generic s.Store(1); // Prints Specific } } 

有没有办法让它在通过界面调用时使用专门版的Store? 如果没有,有没有人知道C#以这种方式对待通用参数的确切原因?

编辑:如果C#无法在多个步骤中解析模板参数,则可以解决该问题。

 void Foo(T t) { SubFoo(t); } void SubFoo(T t) { Console.WriteLine("Generic"); } void SubFoo(int t) { Console.WriteLine("Specific"); } 

在这里调用Foo(1)也将打印“Generic”,编译器是否应该能够解决这个问题? 或者JIT是否会阻止这种情况?

重载解析在编译时执行,而不是在运行时根据传递值的实际类型执行。

 IStorage i = new Storage(); i.Store("somestring"); // Prints Generic i.Store(1); // Prints Generic 

这将始终调用“通用”方法,因为在IStorage只有一个Store重载,并且编译器不知道i实际上包含一个Storage对象。 编译器如何知道Storage的其他重载?

 Storage s = (Storage)i; s.Store("somestring"); // Prints Generic s.Store(1); // Prints Specific 

这里,编译器知道s包含一个Storage对象(或者一个派生自Storage对象),因为s是这样声明的。 所以它看到两个重载。 它为int值选择特定的重载,因为重载决策规则说优先于特定的重载而不是泛​​型的重载。


从技术上讲,可以在运行时确定generics方法中的typeof(T) ,并将方法调用转发给特定方法。 但是如果你考虑一下,这并没有多大意义。 generics方法意味着相同的实现适用于不同的,不相关的类型的参数。 如果您需要针对不同类型的不同实现,则不应使用generics。


 void Foo(T t) { SubFoo(t); } void SubFoo(T t); void SubFoo(int t); 

generics与C ++模板的工作方式有很大不同。 C#编译器只编译一次Foo – 一个generics方法。 请记住:generic表示不同类型的相同实现。 如果T将是一个int或一个string或任何其他类型,C#编译器在编译时不知道。 因此,适用于任何T的唯一可能的Foo实现是调用SubFoo 。 如果根据T调用其中一个SubFoo重载,则对于所有T,Foo的实现将不再相同。

为什么通用的基于代码的专业化在现实世界中特别是在扩展方法中有很大意义?

我将以集合为例,因为evrybody会或多或少地讨论.NET集合。

我将采用.Last(this IEnumerable<> coll)扩展方法的简单示例。 在.NET Framework中,此方法使用代码内类型特化。

首先,关于类型专业化的好处,这个例子非常清楚。 一些可枚举的集合需要扫描整个集合并返回最后一个元素,基于数组只需返回数组的最后一个已分配元素,许多链表有一个指向最后一个元素的指针…所以实现一个带类型特化的generics可以使.Last()方法更有效。

第二,因为这种方法是静态的,对每种集合类型或接口有许多实现都不能解决正确方法选择的问题。 实际上,在编译时基于coll对象的表观类型来选择正确的方法。 如果你想象,你想在List<>上应用连续的扩展方法,第一个可能不需要很多每个集合类型的专用实现,并使用一个基于IEnumerable<>的单个。 所以即使我们有一个.Last(this List<> coll) ,第一个非专用的扩展方法将返回一个IEnumerable<>而专门的.Last(this List<> coll)将不能用于List<>

因此,如果您的代码使用外部程序集(甚至是.NET Framework本身),如果您必须在两周内为复杂的架构问题提供解决方案……您将完美的领域留在现实世界中。 而generics类型专业化成为一种不容忽视的选择。

如果您想利用编译时重载解析,您也可以使用带有int的方法扩展接口:

 public interface IStorage { void Store(T data); } public interface IIntStorage: IStorage { void Store(int data); } public class Storage : IIntStorage { public void Store(T data) { Console.WriteLine("Generic"); } public void Store(int data) { Console.WriteLine("Specific"); } } 

现在,如果你通过IIntStorage接口调用Store(1) ,它将使用专门的方法(类似于你直接调用Storage的方法),但如果你通过IStorage调用它,它仍将使用通用版本。

你可以这样做:

 public interface IStorage { void Store(object data); void Store(T data); } public class Storage : IStorage { public void Store(object data) { Console.WriteLine("Generic"); } public void Store(int data) { Console.WriteLine("Specific"); } } 

您已将i键入为IStorage,并且该接口未定义重载的Store方法。

因为在某些情况下C#generics是运行时模板,所以应该使用运行时特化。 例如,在通用静态方法中,inheritance和接口不可用。 如果要专门化generics静态方法 – 特别是扩展方法 – 则必须使用以下结构检测代码中的类型:

if(typeof(T)== typeof(bool))

对于引用类型的特化(例如字符串)和参数T数据,您会优先:

string s = data as string; if(s!= null)

在这个例子中,问题来自于T和bool在专门代码中的转换:你知道T是bool但语言不允许在这些类型之间进行转换。 解决方案来自对象类型:可以将对象转换为任何类型(在运行时检查转换,在这种情况下不在编译时检查)。 所以,如果你有

T数据;

你可以写:

bool b =(bool)(对象)数据; 数据=(T)(对象)B;

这并不完美:如果类型相等非常快,在某些情况下,您必须测试T是否是指定类型的派生类型(稍长一些)。 当T是像bool这样的值类型时,强制转换为对象,然后返回到类型的平均值类型装箱/拆箱和运行时类型检查引用类型。 运行时优化器可以删除这些非必要步骤,但我不能说它们是否这样做。

根据静态方法的用法,请记住您可以应用T:…对参数化类型的限制。 并且默认值(T)对于布尔值返回false,对于数字基类型返回零,对于引用类型返回null。

运行时专业化意味着额外的测试步骤和装箱/拆箱/运行时类型检查,因此它不是灵丹妙药,但在许多情况下允许在可接受的时间内使用太专业的通用方法:长时间操作(特别是优化)或隐藏或类型复杂性管理的分组比性能更重要。