【问题标题】:Is it possible to write a generic function in .NET that only accepts numerical types?是否可以在.NET 中编写一个只接受数字类型的通用函数?
【发布时间】:2010-10-31 08:16:03
【问题描述】:

假设我想写一个类似下面的函数(像往常一样,一个简单的例子用于说明目的):

Public Function calcSqSum(Of T)(ByVal list As IEnumerable(Of T)) As T
    Dim sumSq As T

    For Each item As T In list
        sumSq += (item * item)
    Next

    Return sumSq
End Function

正如您可能猜到的那样,此函数会导致错误,因为不能保证通用对象实现 + 运算符。不过,据我所知,任何数字类型(整数、双精度、小数等)都可以。

有没有办法编写一个可以接受任何数值类型的(准)泛型函数,而不必自己为每个此类类型显式重载函数?

另外,我想一个同样可以接受的解决方案是检查一个类型是否实现了“+”运算符(或任何通常与数值类型相关联并由函数使用的运算符)。

【问题讨论】:

    标签: .net vb.net generics types


    【解决方案1】:

    C# 10 和 .NET 6:2021 年 11 月起的解决方案

    好消息:.NET 6 和 C# 10 中现在有一个解决方案,参见。 https://devblogs.microsoft.com/dotnet/preview-features-in-net-6-generic-math/#generic-math

    我目前无法估计此功能是否也可以在 VB.NET 中使用。

    为了接受任何数字类型,请使用INumber<T> 接口,例如:

    public T Adding<T>(T a, T b)
       where T : INumber<T>
       where T : IAdditionOperators<T, T, T>
    {
       return a + b;
    }
    

    注意:在我回答时,此功能只是一个预览。 Microsoft 将在 .NET 6 的最终版本中保留这一点,因为他们仍然希望允许突破机会。要使用该功能,必须在项目配置中启用预览功能:

    <Project Sdk="Microsoft.NET.Sdk">
    
      <PropertyGroup>
        <EnablePreviewFeatures>true</EnablePreviewFeatures>
        <LangVersion>preview</LangVersion>
        <OutputType>Exe</OutputType>
        <TargetFramework>net6.0</TargetFramework>
      </PropertyGroup>
    
      <ItemGroup>
        <PackageReference Include="System.Runtime.Experimental" Version="6.0.0-preview.7.21377.19" />
      </ItemGroup>
    
    </Project>
    

    【讨论】:

      【解决方案2】:

      您可以改用重载。为您要支持的每种数字类型编写一个基本相同的函数:

      Public Function calcSqSum(ByVal list As IEnumerable(Of Integer)) As Integer
          Dim sumSq As Integer
          For Each item As Integer In list
              sumSq += (item * item)
          Next
          Return sumSq
      End Function
      
      Public Function calcSqSum(ByVal list As IEnumerable(Of Double)) As Double
          Dim sumSq As Double
          For Each item As Double In list
              sumSq += (item * item)
          Next
          Return sumSq
      End Function
      
      etc
      

      或者,如果它有很多代码,请将您的通用函数设为私有并用重载公共函数包装它:

      Private Function calcSqSum1(Of T)(ByVal list As IEnumerable(Of T)) As T
          Dim sumSq As T
      
          For Each item As T In list
              sumSq += (item * item)
          Next
      
          Return sumSq
      End Function
      
      Public Function calcSqSum(ByVal list As IEnumerable(Of Integer)) As Integer
          Return calcSqSum1(list)
      End Function
      
      Public Function calcSqSum(ByVal list As IEnumerable(Of Double)) As Double
          Return calcSqSum1(list)
      End Function
      
      etc
      

      这不是您所追求的,但公共函数将是类型安全的。

      【讨论】:

        【解决方案3】:

        您可以使用 lambda 表达式,如下所示:

                static T Add<T>(T a, T b)
            {
                // declare the parameters
                ParameterExpression paramA = Expression.Parameter(typeof(T), "a"),
                    paramB = Expression.Parameter(typeof(T), "b");
                // add the parameters together
                BinaryExpression body = Expression.Add(paramA, paramB);
                // compile it
                Func<T, T, T> add = Expression.Lambda<Func<T, T, T>>(body, paramA, paramB).Compile();
                // call it
                return add(a, b);
            }
        

        它不是类型安全的,但它适用于具有预期运算符的类型(加法,在上面的示例中)。

        【讨论】:

          【解决方案4】:

          很抱歉,除非您创建自己的数字类,否则您不能。

          public static T Add<T> (T x, T y) where T: MyNumberClass
          { 
          // your add code
          ...
          }
          

          原因是 .NET 只允许您使用类或接口来约束泛型方法。

          【讨论】:

          • 哦,如果你不想创建你的数字类,你总​​是可以在泛型方法本身内部进行约束检查。您不会出现像“where”语法这样的编译时错误,但它会引发运行时异常。
          【解决方案5】:

          不,因为它们都没有实现特定的通用接口。本质上,框架中没有“数字类型”的真正概念。除非您将它们包装在自定义类中并且让您的方法只接受您的类型(这并不是您问题的直接答案,只是一种解决方法)。

          【讨论】:

          • 我自己再好不过了。
          • 这就是我所怀疑的......那么这是否也意味着没有办法实现我的第二个想法——检查一个类是否实现了特定的运算符?
          • 可以在运行时检查特定的运算符,但不能作为编译时通用约束。没有什么比“where T.operator+”更不可能了。
          • 顺便说一句,如果你想防止包装,你可以让类更灵活(删除约束)并使用反射(本质上是鸭子打字)盲目地调用 operator +。我确定我已经看到了在 SO 上执行此操作的计算类的实现。搜索它。
          猜你喜欢
          • 1970-01-01
          • 2011-02-24
          • 1970-01-01
          • 2016-07-07
          • 2014-12-13
          • 2021-02-09
          • 1970-01-01
          • 1970-01-01
          • 2023-03-28
          相关资源
          最近更新 更多