简体   繁体   English

C#泛型接口特化

[英]C# generic interface specialization

I wonder if it is in any way possible to specialize generic interface methods somehow in C#?我想知道是否有可能以某种方式在 C# 中以某种方式专门化通用接口方法? I have found similar questions, but nothing exactly like this.我发现了类似的问题,但没有完全像这样。 Now I suspect that the answer is "No, you can't" but I would like to have it confirmed.现在我怀疑答案是“不,你不能”,但我想确认一下。

What I have is something like the following.我所拥有的类似于以下内容。

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
    }
}

Is there any way to make it use the specialized version of Store when called through the interface?当通过接口调用时,有没有办法让它使用专门版本的 Store? And if not, does anyone know the exact reason why C# treats Generic arguments this way?如果没有,有人知道 C# 以这种方式处理泛型参数的确切原因吗?

Edit: The issue could be worked around if it wasn't so that C# cannot resolve template arguments in more than one step.编辑:如果不是这样,C# 无法在多个步骤中解析模板参数,则可以解决该问题。

void Foo<T>(T t)
{
    SubFoo(t);
}

void SubFoo<T>(T t)
{
    Console.WriteLine("Generic");
}

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

A call to Foo(1) here will print "Generic" as well, shouldn't the compiler be able to resolve this?在这里调用 Foo(1) 也会打印“Generic”,编译器不应该能够解决这个问题吗? Or does the JIT prevent this?或者 JIT 是否阻止了这种情况?

Overload resolution is performed at compile-time, not at run-time based on the actual type of the passed value.重载解析在编译时执行,而不是在运行时根据传递值的实际类型执行。

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

This will always call the "generic" method, because there is only one overload of Store in IStorage and the compiler doesn't know that i actually contains a Storage object.这将始终调用“通用”方法,因为IStorage只有一个Store重载并且编译器不知道i实际上包含一个Storage对象。 How can the compiler know about the other overload in Storage ?编译器如何知道Storage的其他重载?

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

Here, the compiler knows that s contains a Storage object (or one deriving from Storage ), because s is declared that way.在这里,编译器知道s包含一个Storage对象(或一个从Storage派生的对象),因为s是这样声明的。 So it sees two overloads.所以它看到了两个重载。 It chooses the specific overload for int values, because overload resolution rules say to prefer specific overloads over generic overloads.它为int值选择特定的重载,因为重载解析规则说比通用重载更喜欢特定的重载。


It's technically possible to determine typeof(T) in the generic method at run-time and forward the method call to a specific method.在运行时确定泛型方法中的typeof(T)并将方法调用转发到特定方法在技术上是可能的。 But if you think about it, this doesn't make a lot of sense.但如果你仔细想想,这并没有多大意义。 A generic method means that the same implementation works for arguments of different, unrelated types.泛型方法意味着相同的实现适用于不同的、不相关的类型的参数。 If you want different implementations for different types, you shouldn't use generics for this.如果您想要不同类型的不同实现,则不应为此使用泛型。


void Foo<T>(T t)
{
    SubFoo(t);
}

void SubFoo<T>(T t);
void SubFoo(int t);

Generics work quite a bit different from C++ templates.泛型的工作方式与 C++ 模板有很大不同。 The C# compiler compiles Foo only once -- to a generic method. C# 编译器只将 Foo 编译一次——编译成一个泛型方法。 Remember: generic means same implementation for different types.请记住:泛型意味着不同类型的相同实现。 The C# compiler does not know at compile-time if T is going to be an int or a string or any other type. C# 编译器在编译时不知道 T 是int还是string或任何其他类型。 So the only possible implementation of Foo that works for any T is to call SubFoo<T>.因此,适用于任何 T 的 Foo 的唯一可能实现是调用 SubFoo<T>。 If one of the SubFoo overloads would be called depending on T, the implementation of Foo wouldn't be the same for all T any more.如果根据 T 调用 SubFoo 重载之一,则所有 T 的 Foo 实现将不再相同。

Why Generic code-based specialization make a lot of sense in real world and in particular, in extension methods ?为什么基于通用代码的专业化在现实世界中很有意义,特别是在扩展方法中?

I will take an example on collections because evrybody kowns more or less .NET collections.我将举一个关于集合的例子,因为每个人都或多或少知道 .NET 集合。

I will take the simple example of the .Last(this IEnumerable<<T>> coll) extension method.我将采用.Last(this IEnumerable<<T>> coll)扩展方法的简单示例。 In .NET Framework, this method use in-code type specialization.在 .NET Framework 中,此方法使用代码内类型特化。

First, concerning the benefit of type specialization, this example is quite clear.首先,关于类型特化的好处,这个例子很清楚。 Some enumerable collections need to scan the whole collection and return the last element, array based one need only to return the last allocated element of the array, many linked list have a pointer to the last element... So implementing a generic with type specialization can make the .Last() method by far more efficient.一些可枚举的集合需要扫描整个集合并返回最后一个元素,基于数组的只需要返回数组的最后一个分配元素,许多链表都有一个指向最后一个元素的指针......所以实现了具有类型特化的泛型可以使.Last()方法效率更高。

Second because this method is static, having many implementations for each collection type or interfaces wouldn't solve the problem of right method selection.其次,因为这个方法是静态的,每个集合类型或接口都有很多实现并不能解决正确方法选择的问题。 In effect, selection of the right method is done at compile time based on apparent type of coll object.实际上,正确方法的选择是在编译时根据 coll 对象的明显类型完成的。 If you imagine, you want to apply consecutive extensions methods on a List<<T>> , the first one may not need many per collection type specialized implementations and use a single one based on IEnumerable<<T>> .如果你想像,你想在List<<T>>上应用连续的扩展方法,第一个可能不需要每个集合类型的许多特殊实现,并使用一个基于IEnumerable<<T>> So even if we have a .Last(this List<<T>> coll) , the first non specialized extension method will return a IEnumerable<<T>> and the specialized .Last(this List<<T>> coll) will not be used for the List<<T>> .所以即使我们有一个.Last(this List<<T>> coll) ,第一个非专用扩展方法将返回一个IEnumerable<<T>>并且专用的.Last(this List<<T>> coll)将不能用于List<<T>>

So if your code use external assemblies (even .NET Framework itself), if you have to provide a solution in two weeks to a complex architectural problem ... you leave the domain of the perfection to enter in the real world.因此,如果您的代码使用外部程序集(甚至 .NET Framework 本身),如果您必须在两周内为复杂的体系结构问题提供解决方案……您将离开完美领域进入现实世界。 And generic type specialization become an not to ignore option.泛型类型特化成为一个不容忽视的选择。

If you want to take advantage of compile-time overload resolution you may as well extend the interface with a method that takes an int :如果您想利用编译时重载解析的优势,您也可以使用采用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");
    }
}

Now if you call Store(1) through the IIntStorage interface it will use the specialized method (similar to how you called Storage 's method directly), but if you call it through IStorage it will still use the generic version.现在,如果您通过IIntStorage接口调用Store(1)它将使用专门的方法(类似于您直接调用Storage的方法的方式),但是如果您通过IStorage调用它,它将仍然使用通用版本。

You could do something like this:你可以这样做:

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");
    }
}

You've typed i as IStorage and that interface doesn't define the overloaded Store method.您已将 i 键入为 IStorage 并且该接口未定义重载的 Store 方法。

You can do it by introducing additional type information (eg implementing an interface).您可以通过引入其他类型信息(例如实现接口)来实现。 Here is an example.这是一个例子。

// no need to modify the original interface
public interface IStorage
{
    void Store<T>(T data);
}

Implementation with specialization based on a generic interface基于通用接口的专业化实现

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");
    }
}

Results结果

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"
}

Because C# generics are runtime templates in some circumstances you should use runtime specialization.由于 C# 泛型在某些情况下是运行时模板,因此您应该使用运行时特化。 For example, in generic static methods, inheritance and interfaces aren't usable.例如,在通用静态方法中,继承和接口不可用。 If you want to specialize generic static methods - in particular extension methods - you have to detect the type in code with constructs like :如果您想专门化通用静态方法 - 特别是扩展方法 - 您必须使用以下结构检测代码中的类型:

if (typeof(T)==typeof(bool))如果 (typeof(T)==typeof(bool))

For specialization of reference types (like string for example), and an argument T data, you would prefere:对于引用类型(例如字符串)和参数 T 数据的特化,您更喜欢:

string s = data as string;字符串 s = 数据作为字符串; if (s!=null)如果(s!=空)

In this example, a problem come from conversion between T and bool in specialized code : You know that T is bool but the language doesn't allow conversion between those types.在这个例子中,一个问题来自于特殊代码中 T 和 bool 之间的转换:您知道 T 是 bool 但语言不允许在这些类型之间进行转换。 The solution come from the object type: an object can be casted to any type (conversion is checked at runtime and not at compile time in this case).解决方案来自对象类型:可以将对象强制转换为任何类型(在这种情况下,在运行时而不是在编译时检查转换)。 so if you have所以如果你有

T data;数据;

you can write:你可以写:

bool b=(bool)(object)data; bool b=(bool)(object)data; data=(T)(object)b;数据=(T)(对象)b;

This isn't perfect: If type equality is quite fast, in some circumstances you have to test if T is a derived type of a specified type (a little longer).这并不完美:如果类型相等性非常快,在某些情况下,您必须测试 T 是否是指定类型的派生类型(稍长一些)。 And when T is a value type like bool, cast to object and then back to type mean value type boxing/unboxing and runtime type check for reference types.当 T 是像 bool 这样的值类型时,转换为对象,然后返回到类型平均值类型装箱/拆箱和运行时类型检查引用类型。 Runtime optimiser can remove these non necessary steps but I cannot say if they do so.运行时优化器可以删除这些不必要的步骤,但我不能说他们是否这样做了。

Depending on the usage of your static method, remember that you can apply where T: ... restrictions on parametrized types.根据您的静态方法的使用情况,请记住您可以应用 where T: ... 对参数化类型的限制。 And that default(T) return false for boolean, zero for numeric base types and null for reference types.并且 default(T) 对于布尔值返回 false,对于数字基类型返回零,对于引用类型返回 null。

Runtime specialization implies an additional test steps and boxing/unboxing/runtime type checks, so it isn't the panacea but allows too specialize generic methods in an acceptable time in many circumstances: For long operation (in particular for optimization) or when hiding or grouping of types complexity management is more important than performances.运行时专业化意味着额外的测试步骤和装箱/拆箱/运行时类型检查,因此它不是万能药,但在许多情况下允许在可接受的时间内过于专业化泛型方法:对于长时间操作(特别是优化)或隐藏或类型分组复杂性管理比性能更重要。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM