方法的通用约束 [英] Generic constraints on methods

查看:98
本文介绍了方法的通用约束的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

是否可以在方法签名上对通用类型施加约束?
例如类似的东西:

Is there a way to put constraints on generic types on method signatures?
e.g. something like:

public T dosomething(T val1,T val2) where T : int, decimal


似乎只需要类类型而不是基本类型,是否有解决方法?


It seems it only takes class types not primitive types, is there a way around it?

推荐答案

不,显然,您不能这样做.而且我认为它是(极少数)基本的.NET故障之一.在.NET中,这当然是不可能的,但是,您可能知道,在某些语言中,这是可能的.

这是与数字类型没有公共接口这一事实有关的基本限制.整数和浮点数具有不同的操作集,但是即使在类似的类型(例如大小不同)中,操作也不同,因为它们返回的是不同的类型.

原则上,有一种解决方法,但是这将是一个相当可疑的问题,因为与您要从操作数类型中抽象出来的算法相比,这可能需要太多的精力.好吧,这取决于这些算法的价值.这是主意.

首先,您需要创建一个接口,该接口代表几乎所有特定类型集的所有算术运算,这些类型可以是数字,也可以是诸如矢量,矩阵等复杂类型.这就是它的外观:
No, obviously, you cannot do it this way. And I consider it as one of the (very few) fundamental .NET failures. It is certainly impossible in .NET, but, as you probably know, possible in some languages.

This is the fundamental limitation related to the fact that numeric types don''t have common interface. Integer and floating-point numbers have different operation set, but even in similar types, say, of different size, the operations are different because they return different types.

In principle, there is the work-around, but it would be quite questionable, because it might require too much effort compared to the algorithm you want to abstract out of the operand type. Well, it depends on how valuable are those algorithms. Here is the idea.

First, you need to create an interface representing pretty much all the arithmetic operations of certain set of types, which can be numeric, or, say, complex type such as vectors, matrices, etc. This is how it may look:
interface INumericOperationSet<OPERAND> {
    OPERAND Zero { get; }
    OPERAND One { get; }
    OPERAND Sum(OPERAND left, OPERAND right);
    OPERAND Multiplication(OPERAND left, OPERAND right);
    OPERAND Division(OPERAND left, OPERAND right);
    // ... and a lot more
}


现在,假设您同意为要使用的所有类型手动实现它.我将仅针对两种类型显示它,例如


Now let''s assume you a agree to manually implement it for all the types you want to use. I''ll show it only for two types, such as

class DoubleOperationSet : INumericOperationSet<double> {
    public double Zero { get { return 0; } }
    public double One { get { return 1; } }
    public double Sum(double left, double right) { return left + right; }
    public double Multiplication(double left, double right) {
        return left + right; }
    public double Division(double left, double right) { return left / right; }
    // ... and a lot more
}

class SingleOperationSet : INumericOperationSet<Single> {
    public Single Zero { get { return 0; } }
    public Single One { get { return 1; } }
    public Single Sum(Single left, Single right) { return left + right; }
    public Single Multiplication(Single left, Single right) {
        return left + right; }
    public Single Division(Single left, Single right) { return left / right; }
    // ... and a lot more
}


现在,您可以一次使用所有类型集来实现某个泛型类.由于您不能为原始类型提供约束,因此您必须不受约束地提供此类型,但是要提供代表操作集的类型的相应版本;并且此类型将限制为操作数类型:


Now, you can implement some generic class using all the set of types at once. As you cannot provide constraints for the primitive type, you have to supply this type unconstrained, but with corresponding version of the type representing the operation set; and this type will be constraint to the operand type:

static class AlgorithmSet<OPERAND, OPERATION_SET> where OPERATION_SET : INumericOperationSet<OPERAND>, new() {
    
    static OPERATION_SET operations = new OPERATION_SET();
    
    internal static OPERANDSumOfElements(
        System.Collections.Generic.IList<OPERAND> collection) {
            OPERAND value = operations.Zero;
            foreach(var element in collection)
                value = operations.Sum(value, element);
            return value;
    }

    // ...
    // say, for ProductOfElements, you will need to use
    // INumericOperationSet.Multiplication and INumericOperationSet.One
    // and so on...

} //class AlgorithmSet



这里的问题是您需要以令人沮丧的愚蠢的方式实施和重复实施大量的操作.一个小问题是需要调用操作集构造函数(请参见上面的最后一个代码片段中的new OPERATION_SET()),因为实现的接口方法是非静态的.
更大的问题是浮点数和整数值之间的区别以及一般问题:许多运算不适用于整数(System.Math实际上仅适用于浮点值),甚至更少的运算适用于无符号整数(简单来说:不能真正将减法应用于无符号整数,因为结果可能超出设置的值).但是,实际上,我们可以忽略这样的问题,因为有很多操作不能对值集执行:简单地(检查)两个长整数值的加法会溢出结果.

一方面,它减少了工作量:您不能真正使用太多类型,但另一方面,它减少了方法的价值.就我个人而言,我对这种方法的适用性表示怀疑.我很难想象这真的值得付出努力.但是无论如何,我回答了你的问题. :-)



请注意,它确实为任何类型的通用参数(包括原始类型)创建了一种约束,可以将其称为对固定类型集的约束.换句话说,您可以通过第二个通用参数来约束某些通用参数,因此首先泛型参数只能是某个列表中的一种,而第二种应该匹配.让我们看一下它在纯情况下的外观:



The problem here is big number of operations you need to implement and repeat the implementations in the frustratingly dumb way. One little problem is the need to call of the operation set constructor (see new OPERATION_SET() in the last code fragment above), because the implemented interface methods are non-static.

The bigger problem is the difference between floating-point and integer values and the general problem: many operations are inapplicable to integers (System.Math is really only for floating-point values), and even less are applicable to unsigned integers (simply: you cannot really apply subtraction to unsigned integers, because the result can go outside of the value set). In practice, however, we can ignore such problems, because there are many operations which cannot be performed on the set of values: simply (checked) addition of two long integer value can overflow the result.

For one thing, it reduces the amount of work: there are not too many types you can really use, but from the other hand, it reduced the value of the approach. Personally, I am very skeptical about the applicability of this approach; it''s hard for me to imaging that it would really worth the effort. But anyway, I answered your question. :-)



Note that it really creates a kind of constraint for any types of generic parameters, including primitive types, which can be called "constraints to the fixed set of types. In other words, you can constraint some generic parameter by second generic parameters, so first generic parameter will be only of one type from some list, and the second one should match. Let''s see how it can look in its pure case:

interface IConstraint<OPERAND> {}
// starting to create a list of types
class CharConstraint : IConstraint<char> {}
class IntConstraint : IConstraint<int> {}
class StringConstraint : IConstraint<string> {}
// list of types is complete


现在我们可以创建任何类型的泛型类,该类只能用上面列出的三种类型中的任何一种进行实例化,更确切地说,只能使用这三对类型中的任何一种进行实例化.例如,使类具有操作数约束


Now we can create any type of generic class which can only be instantiated only with any of the three types listed above, more exactly, only with any of the three pairs of types. For example, having the class with operand constraint

static class ConstraintUser<OPERAND, CONTRAINT>
    where CONTRAINT : IConstraint<OPERAND> {
    internal static string OperandToString(OPERAND operand) {
        return operand.ToString(); }
}

我们只能将它的三个不同的实例化为一个完整的类型,如

we can have only three different instantiations of it to a complete type, as in

string stringRepresentaton =
    ConstraintUser<char, CharConstraint>.OperandToString('a');
stringRepresentaton =
    ConstraintUser<int, IntConstraint>.OperandToString(22);
stringRepresentaton =
    ConstraintUser<string, StringConstraint>.OperandToString("Anything");


这样,问题就正式解决了.但这有实际意义吗?几乎没有.这是为什么:我们设计的约束的类型集中的类型有什么共同点?答案:它们都是从CLR根类型System.Object派生的,因此在受约束的通用类中,它们只能使用System.Object的功能.但是,为什么不简单地在需要的地方不使用约束和装箱呢?

好吧,人们可以发挥自己的幻想来发明一些应用.例如,将某些通用类型(如ConstraintUser)限制为两个字节的字符,这是某些专用流所必需的;或者,您可以将类型限制为某些旧的非泛型API使用的集合,该集合应该像这样包装.也许仅是原理性可能性的说明比应用程序更重要时就是这种情况.

—SA


So, the problem is formally solved. But does it make any practical sense? Hardly too much. Here is why: what the types in the type set of the constraint we designed have in common? The answer: they are all derived from the CLR root type: System.Object, so inside the constrained generic class they can only use functionality of System.Object. But then, why not simply using no constraint and boxing where it is required?

Well, one can exercise one''s fantasy to invent some applications. For example, limit some generic type like ConstraintUser to the two-byte characters, which is required by some specialized streams; or you can limit the types to the set used by some old non-generic API which should be wrapped around, something like that. Perhaps this is the case when just the demonstration of the principle possibility is more important than the applications.

—SA


这篇关于方法的通用约束的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆