3

我无法通过搜索找到答案,但也许我问的方式不对。我有一个包含大量矩阵类型函数的程序,这些函数接受 3D 矩阵(以 1D-2D 锯齿状数组的形式)并对元素执行函数。然而,我经常需要使用相同的函数,但使用不同类型的参数:int[][,] 和 float[][,] 和 double[][,]。

到目前为止,我只是重写了相同的方法,但改变了类型,但我有很多这样的东西,不断重写“重新输入”的方法真的很痛苦。

private float SomeFunctionA(float[][,] d)
{
    float sum = 0; 
    for (int k = 0; k < d.GetLength(0); k++)
        for (int j = 0; j < d[0].GetLength(1); j++)
            for (int i = 0; i < d[0].GetLength(0); i++)
                 sum += d[k][i,j];
    return SomeFunctionB(sum);
}

private float SomeFunctionA(double[][,] d)
{
    double sum = 0; 
    for (int k = 0; k < d.GetLength(0); k++)
        for (int j = 0; j < d[0].GetLength(1); j++)
            for (int i = 0; i < d[0].GetLength(0); i++)
                 sum += d[k][i,j];
    return SomeFunctionB(sum);
}

有没有更简单的方法来允许不同的类型?如果有一种方法可以拥有一个具有该功能的通用 main 方法(即 3 个 for 循环和其他主体代码),然后是采用不同类型并为每种情况调用通用方法的辅助方法,那就太好了。

谢谢大家。

4

4 回答 4

2

不幸的是,对于这些基本的数字类型,并非如此。他们没有实现定义+运算符的接口。您尝试使用反射、动态、传入求和/聚合函数等进行的任何操作都会导致相当大的性能成本。由于诸如此类的矩阵运算在性能非常密集的环境中如此频繁地完成,因此代码的减少很少值得为泛化这些方法付出代价。

于 2012-12-05T17:28:44.207 回答
2

您可以使用通用函数并传递委托来聚合和处理结果。正如 Servy 所说,代表的成本可能很高。您需要衡量并查看以下基本方法是否适合您。如果这些操作的性能很重要,那么保留单独的版本是很好的方法。您还可以检查使用 Jon Skeet 对通用运算符的 MiscUtil 支持是否可以获得可接受的性能。

private T SomeFunctionA<T>(T[][,] d, Func<T, T, T> aggregate, Func<T,T> postProcess)
{
    T sum = default(T); 
    for (int k = 0; k < d.GetLength(0); k++)
        for (int j = 0; j < d[0].GetLength(1); j++)
            for (int i = 0; i < d[0].GetLength(0); i++)
                 sum = aggreagate(sum, d[k][i,j]);
    return postProcess(sum);
}

aggregate(在 LINQ 术语中,其他通用名称“reduce”)是执行原始“sum”,类似于Enumerable.Aggregate具有签名的更通用版本Func<TAccumulate, TSource, TAccumulate>postProcess只是抽象处理结果和代码可以通过调用通用版本来重写,SomeFunctionB如果存在的话。对于原始代码用法将是:

 SomeFunctionA<float>((sum, current) => sum + current, SomeFunctionB);
于 2012-12-05T17:29:01.920 回答
1

C# 泛型中没有运算符约束,所以我将使用下一个:

    private interface ICalculator<T>
    {
        T Add(T x, T y);
        // you may want to add more operations here
    }

    private class Int32Calculator: ICalculator<int>
    {
        public int Add(int x, int y)
        {
            return x + y;
        }
    }

    private int Int32SomeFunction(int [][,] d)
    {
        return SomeFunction<int>(d, new Int32Calculator());
    }

    private T SomeFunction<T>(T[][,] d, ICalculator<T> calculator)
        where T : struct 
    {
        T sum = default(T);
        for (int k = 0; k < d.GetLength(0); k++)
            for (int j = 0; j < d[0].GetLength(1); j++)
                for (int i = 0; i < d[0].GetLength(0); i++)
                    sum = calculator.Add(sum, d[k][i, j]);
        return sum;
    }

对于这个解决方案,您可以创建更多的类,但在这种情况下您可以支持任何运算符(不仅是 +)。

于 2012-12-05T17:37:28.703 回答
1

为什么不使用文本模板?

创建一个名为“Overloads.tt”的新文件:

<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".cs" #>
using System;

namespace MyNamespace 
{
  public partial class Generated 
  {
  <# 
    var desiredTypes = new[] { "int", "float", "double" };
    foreach(var type in desiredTypes) {
  #>
  private <#=type#> SomeFunctionA(<#=type#>[][,] d)
  {
      <#=type#> sum = 0; 
      for (int k = 0; k < d.GetLength(0); k++)
          for (int j = 0; j < d[0].GetLength(1); j++)
              for (int i = 0; i < d[0].GetLength(0); i++)
                    sum += d[k][i,j];
      return SomeFunctionB(sum);
  }

  private <#=type#> SomeFunctionB(<#=type#> input)
  {
    return default(<#=type#>);
  }

  <# } #>
  }
}

当你保存它时,会弹出Overloads.cs- 使用你所有的方法。

这也类似于 C++ 模板的工作方式。

几点注意事项:

该行var desiredTypes = new[] ....创建了一个字符串数组,然后用于驱动生成重载方法的循环。

生成类是有意义的,partial这样您就可以混合更传统的代码。

于 2012-12-05T17:51:07.627 回答