我想知道在C#中以某种方式专门化通用接口方法是否有可能?我发现了类似的问题,但没有完全像这样。现在我怀疑答案是“不,你不能”,但我想确认一下。
我所拥有的是以下内容。
public interface IStorage
{
void Store<T>(T data);
}
public class Storage : IStorage
{
public void Store<T>(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 t)
{
SubFoo(t);
}
void SubFoo<T>(T t)
{
Console.WriteLine("Generic");
}
void SubFoo(int t)
{
Console.WriteLine("Specific");
}
这里对Foo(1)的调用也将打印“Generic”,编译器是否应该能够解决这个问题?或者JIT是否会阻止这种情况?
答案 0 :(得分:19)
重载解析在编译时执行,而不是在运行时根据传递值的实际类型执行。
IStorage i = new Storage();
i.Store("somestring"); // Prints Generic
i.Store(1); // Prints Generic
这将始终调用“generic”方法,因为Store
中只有IStorage
的一个重载,并且编译器不知道i
实际上包含{{1}对象。编译器如何知道Storage
中的其他重载?
Storage
这里,编译器知道Storage s = (Storage)i;
s.Store("somestring"); // Prints Generic
s.Store(1); // Prints Specific
包含s
对象(或从Storage
派生的对象),因为Storage
是这样声明的。所以它看到两个重载。它为s
值选择特定的重载,因为重载决策规则表示优先于特定的重载而不是泛型重载。
技术上可以在运行时确定泛型方法中的int
,并将方法调用转发给特定方法。但是如果你考虑一下,这并没有多大意义。泛型方法意味着相同的实现适用于不同的,不相关的类型的参数。如果您需要针对不同类型的不同实现,则不应使用泛型。
typeof(T)
泛型与C ++模板的工作方式有很大不同。 C#编译器只编译一次Foo - 一个泛型方法。请记住:generic表示不同类型的相同实现。如果T将是void Foo<T>(T t)
{
SubFoo(t);
}
void SubFoo<T>(T t);
void SubFoo(int t);
或int
或任何其他类型,C#编译器在编译时不知道。因此,适用于任何T的唯一可能的Foo实现是调用SubFoo&lt; T&gt;。如果根据T调用其中一个SubFoo重载,则对于所有T,Foo的实现将不再相同。
答案 1 :(得分:2)
为什么基于代码的通用专业化在现实世界中特别是在扩展方法中有很大意义?
我将以集合为例,因为evrybody会或多或少知道.NET集合。
我将采用.Last(this IEnumerable<<T>> coll)
扩展方法的简单示例。在.NET Framework中,此方法使用代码内类型特化。
首先,关于类型专业化的好处,这个例子非常清楚。一些可枚举的集合需要扫描整个集合并返回最后一个元素,基于数组只需要返回数组的最后一个分配元素,许多链表都有一个指向最后一个元素的指针...所以实现一个带有类型特化的泛型可以使.Last()
方法更有效率。
第二,因为这个方法是静态的,对每个集合类型或接口有很多实现都不能解决正确方法选择的问题。实际上,在编译时基于coll对象的表观类型来选择正确的方法。如果你想象,你想在List<<T>>
上应用连续的扩展方法,第一个可能不需要很多每个集合类型的专用实现,并使用基于IEnumerable<<T>>
的单个方法。因此,即使我们有.Last(this List<<T>> coll)
,第一个非专业扩展方法也会返回IEnumerable<<T>>
,而专门的.Last(this List<<T>> coll)
将不会用于List<<T>>
。
因此,如果您的代码使用外部程序集(甚至是.NET Framework本身),如果您必须在两周内提供解决复杂架构问题的解决方案......您将完美的领域留在现实世界中。而泛型类型专业化成为一种不容忽视的选择。
答案 2 :(得分:1)
如果您想利用编译时重载解析,您也可以使用int
的方法扩展接口:
public interface IStorage
{
void Store<T>(T data);
}
public interface IIntStorage: IStorage
{
void Store(int data);
}
public class Storage : IIntStorage
{
public void Store<T>(T data)
{
Console.WriteLine("Generic");
}
public void Store(int data)
{
Console.WriteLine("Specific");
}
}
现在,如果您通过Store(1)
界面调用IIntStorage
,它将使用专门的方法(类似于您直接调用Storage
方法的方式),但如果您通过{ {1}}它仍将使用通用版本。
答案 3 :(得分:0)
你可以这样做:
public interface IStorage<T>
{
void Store(object data);
void Store<T>(T data);
}
public class Storage : IStorage<int>
{
public void Store(object data)
{
Console.WriteLine("Generic");
}
public void Store(int data)
{
Console.WriteLine("Specific");
}
}
您已将i键入为IStorage,并且该接口未定义重载的Store方法。
答案 4 :(得分:0)
您可以通过引入其他类型信息(例如,实现接口)来实现。这是一个例子。
// no need to modify the original interface
public interface IStorage
{
void Store<T>(T data);
}
基于通用接口的专业化实现
public class Storage : IStorage,
Storage.ISpecializedFor<int>,
Storage.ISpecializedFor<double>
{
// a private interface to provide additional type info
private interface ISpecializedFor<T>
{
void Store(T data);
}
public void Store<T>(T data)
{
// resolve specialization
if (this is ISpecializedFor<T> specialized)
{
specialized.Store(data);
}
else
{
// unspecialized implementation goes here
Console.WriteLine("Unspecialized");
}
}
// specialized implementation
void ISpecializedFor<int>.Store(int data)
{
Console.WriteLine("Specialized for int");
}
void ISpecializedFor<double>.Store(double data)
{
Console.WriteLine("Specialized for double");
}
}
结果
void Main()
{
IStorage storage = new Storage();
storage.Store("hello"); // prints "Unspecialized"
storage.Store(42); // prints "Specialized for int"
storage.Store(1.0); // prints "Specialized for double"
}
答案 5 :(得分:-1)
因为在某些情况下C#泛型是运行时模板,所以应该使用运行时特化。例如,在通用静态方法中,继承和接口不可用。如果你想专门化泛型静态方法 - 特别是扩展方法 - 你必须使用如下构造检测代码中的类型:
if(typeof(T)== typeof(bool))
对于引用类型的特化(例如字符串)和参数T data,你会优先:
string s = data as string; if(s!= null)
在这个例子中,问题来自于T和bool在专门代码中的转换:你知道T是bool但语言不允许在这些类型之间进行转换。解决方案来自对象类型:对象可以转换为任何类型(在运行时检查转换,在这种情况下不在编译时检查)。所以,如果你有
T数据;
你可以写:
bool b =(bool)(对象)数据; 数据=(T)(对象)B; </ P>
这并不完美:如果类型相等非常快,在某些情况下,您必须测试T是否是指定类型的派生类型(稍长一些)。当T是像bool这样的值类型时,强制转换为对象,然后返回到类型的平均值类型装箱/拆箱和运行时类型检查引用类型。运行时优化器可以删除这些非必要步骤,但我不能说它们是否这样做。
根据静态方法的用法,请记住您可以应用T:...对参数化类型的限制。并且默认值(T)对于布尔值返回false,对于数字基类型返回零,对于引用类型返回null。
运行时专业化意味着额外的测试步骤和装箱/拆箱/运行时类型检查,因此它不是灵丹妙药,但在许多情况下在可接受的时间内允许过于专业化的通用方法:对于长时间操作(特别是对于优化)或当隐藏或分组类型时,复杂性管理比表演更重要。