有人能告诉我是否有一种方法可以用泛型将泛型类型参数T限制为:

Int16 Int32 Int64 UInt16 UInt32 UInt64

我知道哪里关键字,但不能找到只有这些类型的接口,

喜欢的东西:

static bool IntegerFunction<T>(T value) where T : INumeric 

当前回答

这个约束存在于。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;
}

innumber在系统中。数字名称空间。

还有诸如IAdditionOperators和IComparisonOperators这样的接口,因此您可以通用地使用特定的操作符。

其他回答

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

更进一步说,我们需要

static bool GenericFunction<T>(T value) 
    where T : operators( +, -, /, * )

甚至

static bool GenericFunction<T>(T value) 
    where T : Add, Subtract

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

你可以把数字包装在其他东西(类似于INullable<T>),就像这里的codeproject。


您可以在运行时应用该限制(通过对操作符进行反射或检查类型),但这首先就失去了使用泛型的优势。

也许你能做的就是

static bool IntegerFunction<T>(T value) where T: struct

不确定你是否能做到以下几点

static bool IntegerFunction<T>(T value) where T: struct, IComparable
, IFormattable, IConvertible, IComparable<T>, IEquatable<T>

对于如此特定的内容,为什么不为每种类型设置重载呢?列表很短,而且可能占用更少的内存。

.NET 6有一个预览功能:

https://devblogs.microsoft.com/dotnet/preview-features-in-net-6-generic-math/#generic-math

下面是文章中的一个例子:

static T Add<T>(T left, T right)
    where T : INumber<T>
{
    return left + right;
}

INumber是一个实现其他接口的接口,比如IAdditionOperators,它允许通用的+用法。现在这是可能的,因为另一个预览特性是接口中的静态抽象,因为+操作符重载是一个静态方法:

/// <summary>Defines a mechanism for computing the sum of two values.</summary>
/// <typeparam name="TSelf">The type that implements this interface.</typeparam>
/// <typeparam name="TOther">The type that will be added to <typeparamref name="TSelf" />.</typeparam>
/// <typeparam name="TResult">The type that contains the sum of <typeparamref name="TSelf" /> and <typeparamref name="TOther" />.</typeparam>
[RequiresPreviewFeatures(Number.PreviewFeatureMessage, Url = Number.PreviewFeatureUrl)]
public interface IAdditionOperators<TSelf, TOther, TResult>
    where TSelf : IAdditionOperators<TSelf, TOther, TResult>
{
    /// <summary>Adds two values together to compute their sum.</summary>
    /// <param name="left">The value to which <paramref name="right" /> is added.</param>
    /// <param name="right">The value which is added to <paramref name="left" />.</param>
    /// <returns>The sum of <paramref name="left" /> and <paramref name="right" />.</returns>
    static abstract TResult operator +(TSelf left, TOther right);
}

我有一个类似的情况,我需要处理数字类型和字符串;看起来有点奇怪,但就是这样。

和许多人一样,我研究了约束条件,提出了一堆它必须支持的接口。然而,第一,它并不是100%无懈可击的;第二,任何新看到这一长串约束条件的人都会立刻感到困惑。

所以,我的方法是把我所有的逻辑放到一个没有约束的泛型方法中,但是让这个泛型方法是私有的。然后我用公共方法公开它,其中一个方法显式地处理我想处理的类型——在我看来,代码是干净而显式的,例如。

public static string DoSomething(this int input, ...) => DoSomethingHelper(input, ...);
public static string DoSomething(this decimal input, ...) => DoSomethingHelper(input, ...);
public static string DoSomething(this double input, ...) => DoSomethingHelper(input, ...);
public static string DoSomething(this string input, ...) => DoSomethingHelper(input, ...);

private static string DoSomethingHelper<T>(this T input, ....)
{
    // complex logic
}

考虑到这个问题的受欢迎程度和这样一个函数背后的兴趣,我很惊讶地看到,还没有涉及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;
        }
    <#
    } #>
}

就是这样。你现在完成了。

保存这个文件会自动编译成这个源文件:

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

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

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

我先说一句:不,这并没有违反DRY原则。DRY原则的存在是为了防止人们在多个地方复制代码,从而导致应用程序变得难以维护。

这里的情况完全不同:如果您想要更改,那么您只需更改模板(对于您的所有生成都是一个单一的源代码!),然后就完成了。

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

<#@ import namespace="TheNameSpaceYouWillUse" #>
<#@ assembly name="$(TargetPath)" #>

说实话:这太酷了。

免责声明:这个示例受到了Kevin Hazzard和Jason Bock, Manning Publications在。net中的元编程的严重影响。