是否存在将我的泛型方法限制为数字类型的约束?

Cor*_*kie 350 c# generics constraints

任何人都可以告诉我是否有一种方法可以使用泛型来限制泛型类型参数T:

  • Int16
  • Int32
  • Int64
  • UInt16
  • UInt32
  • UInt64

我知道的where关键字,但无法找到一个接口只有这些类型,

就像是:

static bool IntegerFunction<T>(T value) where T : INumeric 
Run Code Online (Sandbox Code Playgroud)

Kon*_*lph 132

Hejlsberg 在接受 Bruce Eckel 采访时描述了不实现该功能的原因.

不过,我不得不承认,我不知道他认为他提议的解决方法是如何工作的.他的建议是将算术运算推迟到其他一些通用类(阅读采访!).这有什么用?恕我直言,并不多.

  • 顺便说一句,MiscUtil提供了一个完全符合这个要求的通用类; `Operator` /`算<T>`; http://www.yoda.arachsys.com/csharp/miscutil/usage/genericoperators.html (25认同)
  • 我不同意Heijsberg的短语"所以在某种意义上,C++模板实际上是无类型的,或者是松散类型的.而C#泛型是强类型的." 这真的是推广C#的营销BS.强/弱打字与诊断质量无关.否则:有趣的发现. (14认同)

Jer*_*vel 95

考虑到这个问题的普及以及这种功能背后的兴趣,我很惊讶地发现还没有涉及T4的答案.

在这个示例代码中,我将演示一个非常简单的示例,说明如何使用强大的模板引擎来完成编译器在后台使用泛型执行的操作.

您可以简单地为您喜欢的每种类型生成所需的函数,并相应地使用它(在编译时!),而不是通过箍和牺牲编译时的确定性.

为此:

  • 创建一个名为GenericNumberMethodTemplate.tt的新文本模板文件.
  • 删除自动生成的代码(您将保留大部分代码,但不需要某些代码).
  • 添加以下代码段:
<#@ template language="C#" #>
<#@ output extension=".cs" #>
<#@ assembly name="System.Core" #>

<# Type[] types = new[] {
    typeof(Int16), typeof(Int32), typeof(Int64),
    typeof(UInt16), typeof(UInt32), typeof(UInt64)
    };
#>

using System;
public static class MaxMath {
    <# foreach (var type in types) { 
    #>
        public static <#= type.Name #> Max (<#= type.Name #> val1, <#= type.Name #> val2) {
            return val1 > val2 ? val1 : val2;
        }
    <#
    } #>
}
Run Code Online (Sandbox Code Playgroud)

而已.你现在完成了.

保存此文件将自动将其编译为此源文件:

using System;
public static class MaxMath {
    public static Int16 Max (Int16 val1, Int16 val2) {
        return val1 > val2 ? val1 : val2;
    }
    public static Int32 Max (Int32 val1, Int32 val2) {
        return val1 > val2 ? val1 : val2;
    }
    public static Int64 Max (Int64 val1, Int64 val2) {
        return val1 > val2 ? val1 : val2;
    }
    public static UInt16 Max (UInt16 val1, UInt16 val2) {
        return val1 > val2 ? val1 : val2;
    }
    public static UInt32 Max (UInt32 val1, UInt32 val2) {
        return val1 > val2 ? val1 : val2;
    }
    public static UInt64 Max (UInt64 val1, UInt64 val2) {
        return val1 > val2 ? val1 : val2;
    }
}
Run Code Online (Sandbox Code Playgroud)

在您的main方法中,您可以验证您是否具有编译时确定性:

namespace TTTTTest
{
    class Program
    {
        static void Main(string[] args)
        {
            long val1 = 5L;
            long val2 = 10L;
            Console.WriteLine(MaxMath.Max(val1, val2));
            Console.Read();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

我会提前一句话:不,这不违反DRY原则.DRY原则是为了防止人们在多个地方复制代码,导致应用程序难以维护.

这里的情况并非如此:如果您想要进行更改,那么您只需更改模板(所有代的单一来源!)即可完成.

要将它与您自己的自定义定义一起使用,请向生成的代码添加名称空间声明(确保它与您定义自己的实现的声明相同)并将该类标记为partial.然后,将这些行添加到模板文件中,以便它包含在最终编译中:

<#@ import namespace="TheNameSpaceYouWillUse" #>
<#@ assembly name="$(TargetPath)" #>
Run Code Online (Sandbox Code Playgroud)

老实说:这很酷.

免责声明:此示例受到了来自Kevin Hazzard和Jason Bock,Manning Publications的.NET中元编程的严重影响.

  • 非常酷,我正要开始使用它,然后我想起了我对 Resharper 的重构是多么依赖,并且您不能通过 T4 模板进行重命名重构。这不是关键,但值得考虑。 (3认同)
  • 与基于策略的解决方案不同,此解决方案为+1,因为它保留了内置整体类型的运行效率.如果多次使用(如数学库中),通过附加(可能是虚拟)方法调用内置CLR运算符(如Add)会严重影响性能.由于整数类型的数量是常量(并且不能继承),因此您只需要重新生成错误修复的代码. (2认同)

Kei*_*ith 84

对此没有任何限制.对于想要使用泛型进行数值计算的人来说,这是一个真正的问题.

我会更进一步说我们需要

static bool GenericFunction<T>(T value) 
    where T : operators( +, -, /, * )
Run Code Online (Sandbox Code Playgroud)

甚至

static bool GenericFunction<T>(T value) 
    where T : Add, Subtract
Run Code Online (Sandbox Code Playgroud)

不幸的是,你只有接口,基类和关键字struct(必须是值类型),class(必须是引用类型)和new()(必须有默认构造函数)

您可以将数字包装在codeproject上的其他内容(类似于INullable<T>)中.


您可以在运行时应用限制(通过反映运算符或检查类型),但这确实失去了首先使用泛型的优势.

  • 是的 - Jon Skeet不久前曾向我们指出过其他事情(但在今年之后的回应) - 他们是一个聪明的主意,但我仍然喜欢适当的约束支持. (10认同)
  • 现在这是 .NET 6 中的事情:https://devblogs.microsoft.com/dotnet/preview-features-in-net-6-generic-math/ (3认同)
  • 我想知道你是否看过MiscUtil对通用运营商的支持...... http://www.yoda.arachsys.com/csharp/miscutil/usage/genericoperators.html (2认同)
  • 等等,“其中 T : 运算符(+, -, /, * )”是合法的 C# 吗?抱歉新手问题。 (2认同)
  • @kdbanman 我不这么认为。Keith 说 C# 不支持 OP 所要求的内容,并建议我们应该能够执行“where T :operators(+, -, /, * )”,但不能。 (2认同)

Ser*_*dar 57

使用策略的解决方法:

interface INumericPolicy<T>
{
    T Zero();
    T Add(T a, T b);
    // add more functions here, such as multiplication etc.
}

struct NumericPolicies:
    INumericPolicy<int>,
    INumericPolicy<long>
    // add more INumericPolicy<> for different numeric types.
{
    int INumericPolicy<int>.Zero() { return 0; }
    long INumericPolicy<long>.Zero() { return 0; }
    int INumericPolicy<int>.Add(int a, int b) { return a + b; }
    long INumericPolicy<long>.Add(long a, long b) { return a + b; }
    // implement all functions from INumericPolicy<> interfaces.

    public static NumericPolicies Instance = new NumericPolicies();
}
Run Code Online (Sandbox Code Playgroud)

算法:

static class Algorithms
{
    public static T Sum<P, T>(this P p, params T[] a)
        where P: INumericPolicy<T>
    {
        var r = p.Zero();
        foreach(var i in a)
        {
            r = p.Add(r, i);
        }
        return r;
    }

}
Run Code Online (Sandbox Code Playgroud)

用法:

int i = NumericPolicies.Instance.Sum(1, 2, 3, 4, 5);
long l = NumericPolicies.Instance.Sum(1L, 2, 3, 4, 5);
NumericPolicies.Instance.Sum("www", "") // compile-time error.
Run Code Online (Sandbox Code Playgroud)

该解决方案是编译时安全的.CityLizard Framework提供.NET 4.0的编译版本.该文件是lib/NETFramework4.0/CityLizard.Policy.dll.

它也可以在Nuget中找到:https://www.nuget.org/packages/CityLizard/ .请参见CityLizard.Policy.I结构.


Kap*_*nir 18

从 C# 7.3 开始,您可以使用更接近的近似值-非托管约束来指定类型参数是非指针、不可为 null 的非托管类型。

class SomeGeneric<T> where T : unmanaged
{
//...
}
Run Code Online (Sandbox Code Playgroud)

非托管约束意味着 struct 约束,并且不能与 struct 或 new() 约束结合使用。

如果类型是以下任一类型,则该类型为非托管类型:

  • sbyte、byte、short、ushort、int、uint、long、ulong、char、float、double、decimal 或 bool
  • 任何枚举类型
  • 任何指针类型
  • 任何仅包含非托管类型字段的用户定义结构类型,并且在 C# 7.3 及更早版本中,不是构造类型(至少包含一个类型参数的类型)

要进一步限制并消除未实现 IComparable 的指针和用户定义类型,请添加IComparable(但 enum 仍然是从 IComparable 派生的,因此通过添加 IEquatable < T > 来限制 enum,您可以根据自己的情况进一步添加额外的接口。非托管允许使此列表更短):

    class SomeGeneric<T> where T : unmanaged, IComparable, IEquatable<T>
    {
    //...
    }
Run Code Online (Sandbox Code Playgroud)

但这并不能阻止 DateTime 实例化。


Dan*_*Dan 16

.Net 7 中存在此限制。

查看此.NET 博客文章实际文档

从 .NET 7 开始,您可以使用INumber和等接口IFloatingPoint来创建如下程序:

using System.Numerics;

Console.WriteLine(Sum(1, 2, 3, 4, 5));
Console.WriteLine(Sum(10.541, 2.645));
Console.WriteLine(Sum(1.55f, 5, 9.41f, 7));

static T Sum<T>(params T[] numbers) where T : INumber<T>
{
    T result = T.Zero;

    foreach (T item in numbers)
    {
        result += item;
    }

    return result;
}
Run Code Online (Sandbox Code Playgroud)

INumber位于System.Numerics命名空间中。

还有诸如IAdditionOperators和之类的接口IComparisonOperators,因此您可以一般地使用特定的运算符。


Mar*_*ell 15

这个问题有点像常见问题解答之一,所以我把它作为wiki发布(因为我以前发过类似的,但这是一个较旧的); 无论如何...

您使用的是什么版本的.NET?如果您使用的是.NET 3.5,那么我在MiscUtil中有一个通用运算符实现(免费等).

这有类似的方法T Add<T>(T x, T y),以及不同类型的算术的其他变体(如DateTime + TimeSpan).

此外,这适用于所有内置,提升和定制的操作员,并缓存代表的性能.

关于为什么这很棘手的一些额外背景在这里.

您可能还想知道dynamic(4.0)排序也间接地解决了这个问题 - 即

dynamic x = ..., y = ...
dynamic result = x + y; // does what you expect
Run Code Online (Sandbox Code Playgroud)


ljs*_*ljs 14

不幸的是,您只能在此实例的where子句中指定struct.看起来很奇怪你不能具体指定Int16,Int32等,但我确信在where子句中不允许值类型的决定存在一些深层实现原因.

我想唯一的解决方案是进行运行时检查,不幸的是,这会阻止在编译时拾取问题.那就像: -

static bool IntegerFunction<T>(T value) where T : struct {
  if (typeof(T) != typeof(Int16)  &&
      typeof(T) != typeof(Int32)  &&
      typeof(T) != typeof(Int64)  &&
      typeof(T) != typeof(UInt16) &&
      typeof(T) != typeof(UInt32) &&
      typeof(T) != typeof(UInt64)) {
    throw new ArgumentException(
      string.Format("Type '{0}' is not valid.", typeof(T).ToString()));
  }

  // Rest of code...
}
Run Code Online (Sandbox Code Playgroud)

我知道这有点难看,但至少提供了所需的约束.

我还会研究这种实现可能带来的性能影响,也许还有更快的方法.

  • + 1,但是,如果依赖于约束定义的操作,`//其余代码......'可能无法编译. (13认同)
  • -1; 由于@Nick 给出的原因,这不起作用。当您尝试在 `// 其余代码...` 中执行任何算术运算时,例如 `value + value` 或 `value * value`,就会出现编译错误。 (2认同)

Haa*_*ked 13

可能你最接近的是

static bool IntegerFunction<T>(T value) where T: struct
Run Code Online (Sandbox Code Playgroud)

不确定您是否可以执行以下操作

static bool IntegerFunction<T>(T value) where T: struct, IComparable
, IFormattable, IConvertible, IComparable<T>, IEquatable<T>
Run Code Online (Sandbox Code Playgroud)

对于某些特定的东西,为什么不只是为每种类型都有重载,列表是如此之短,它可能会有更少的内存占用.


Arm*_*our 9

主题是旧的,但对于未来的读者:

此功能与Discriminated Unions目前尚未在 C# 中实现的功能密切相关。我在这里发现了它的问题:

https://github.com/dotnet/csharplang/issues/113

这个问题仍然是开放的,并且已经计划了这个功能 C# 10

所以我们还得再等一会儿,但发布后你可以这样做:

static bool IntegerFunction<T>(T value) where T : Int16 | Int32 | Int64 | ...
Run Code Online (Sandbox Code Playgroud)

  • 我没有看到其中的联系。联合(无论是否“受歧视”)是关于*传递允许包含多种类型之一的值*。通用约束是关于*调用已知类型支持的方法*。相关提案是 [Updating generic math .. for .NET 7](https://github.com/dotnet/designs/pull/257)。不同之处在于,当您调用泛型方法时,就知道您拥有什么类型。不需要工会。例如,在任何类中,编写“public static T MyFunc(T a, T b) { ... }”。在*编译时*`T` 被解析为特定类型。 (2认同)

小智 5

无法将模板限制为类型,但您可以根据类型定义不同的操作。作为通用数字包的一部分,我需要一个通用类来添加两个值。

    class Something<TCell>
    {
        internal static TCell Sum(TCell first, TCell second)
        {
            if (typeof(TCell) == typeof(int))
                return (TCell)((object)(((int)((object)first)) + ((int)((object)second))));

            if (typeof(TCell) == typeof(double))
                return (TCell)((object)(((double)((object)first)) + ((double)((object)second))));

            return second;
        }
    }
Run Code Online (Sandbox Code Playgroud)

请注意,typeofs 是在编译时计算的,因此编译器将删除 if 语句。编译器还会删除虚假的强制转换。所以有些东西会在编译器中解决

        internal static int Sum(int first, int second)
        {
            return first + second;
        }
Run Code Online (Sandbox Code Playgroud)