【问题标题】:Implicit conversion for generic class restricted only to some types泛型类的隐式转换仅限于某些类型
【发布时间】:2017-01-22 19:22:07
【问题描述】:

假设我们对泛型类 Matrix<T> 有想法,其中 T 是数字类型(Complexdoublefloatint 等)。

很自然,我们在 C# 中进行了从 floatdouble、从 doubleComplex 的隐式转换。一般规则是我们有从较小类型到较大类型的隐式转换。到目前为止一切顺利。

现在假设我们正在实现我们的Matrix<T> 类型。由于这种新类型在某种程度上也是数字的(或者至少它包含数值),所以从Matrix<float>Matrix<double>、从Matrix<double>Matrix<Complex> 等进行隐式转换是很自然的。至少它是很高兴将这些用于乘法、加法等数学运算。但这似乎无法正确实现,因为隐式运算符要求至少一种类型与我们在其中实现它的类相同。

示例:即使认为可以解决我的问题,以下代码也无法编译。

public abstract partial class Matrix<T>
{
    /// <summary>
    /// Implicitly converts a matrix to double precision complex numbers.
    /// </summary>
    public static implicit operator Matrix<Complex64>(Matrix<double> matrix)
    {
        matrix.ToComplex();
    }

    /// <summary>
    /// Implicitly converts a matrix to double precision real numbers.
    /// </summary>
    public static implicit operator Matrix<double>(Matrix<float> matrix)
    {
        matrix.ToDouble();
    }
}

它不会编译,因为“CS0556 用户定义的转换必须转换为封闭类型或从封闭类型转换”,假设我很好,因为它是语言规范的一部分,但不应该有任何其他方式实现这个?

例如,这也不编译。

public abstract partial class Matrix<double>
{
    /// <summary>
    /// Implicitly converts a matrix to single precision real numbers.
    /// </summary>
    public static implicit operator Matrix<double>(Matrix<float> matrix)
    {
        matrix.ToDouble();
    }
}

有没有办法实现这个东西,感觉很自然所以我觉得应该可以实现?

现在我创建了一个解决方法,它可以将所有类型隐式转换为最大类型,但不能解决从Matrix&lt;float&gt;Matrix&lt;double&gt; 的转换,它只能解决到Matrix&lt;Complex&gt; 的转换。

public abstract partial class Matrix<T>
{
    /// <summary>
    /// Implicitly converts a matrix to double precision complex numbers.
    /// </summary>
    public static implicit operator Matrix<Complex64>(Matrix<T> matrix)
    {
        return matrix.Map(x =>
        {
            if (x is Numerics.Complex32)
            {
                var xc32 = (Numerics.Complex32)(object)x;
                return new Complex64(xc32.Real, xc32.Imaginary);
            }
            return new Complex64(Convert.ToDouble(x), 0);
        }, Zeros.AllowSkip);
    }
}

如果有人对这个问题的背景感兴趣,你可以看看https://github.com/mathnet/mathnet-numerics/issues/304

解决此问题的另一种选择可能是使用“扩展运算符”(类似于扩展方法)之类的东西,但这些在 C# 中不存在。

【问题讨论】:

  • 老实说,作为这样一个 API 的用户,我担心这样的隐式转换。显式演员表呢?这对你有用吗?
  • @OndrejTucny 不,不幸的是,我认为隐式转换是唯一的方法。

标签: c# .net generics implicit-conversion


【解决方案1】:

我知道创建通用向量或矩阵类并让它进行代数(加法、减法、乘法)的唯一方法是发出调用运算符的MSIL 代码(例如operator +)。然后它可以与定义了static MyType operator + (MyType a, MyTYpe b)任何 类型以及内置类型一起使用。

有关更多详细信息,请参阅this answer of mine 类似问题。

使用Operation&lt;T&gt; 中的静态方法,您可以获得以下示例代码

public class Matrix<T>
{
    T[,] elements;

    static readonly Func<T,T> add = Operation<T>.Add;

    public static Matrix<T> operator + (Matrix<T> A, Matrix<T> B)
    {
       Matrix<T> result = new Matrix<T>(rows,cols);
       for(int i=0; i<rows; i++)
       {
         for(int j=0; j<cols; j++)
         {
            result[i,j] = add(A[i,j], B[i,j]);
         }
       }
       return result;
    }    
    // rest of algebra
}

因此,如果您确实定义了 Complex64 类并定义了 operator +,则可以声明 Matrix&lt;Complex64&gt; 并直接像 var C = A+2*B 那样做代数。运行时将为内置类型或自定义类型调用适当的运算符。速度影响很小,因为在第一次使用时,每种类型只进行一次一次的反射来找到调用的适当方法。

【讨论】:

  • 是的,这看起来是一种有趣的方法,但请注意,我正在分叉已经存在的库,其中大多数运算符已经实现。因此,可悲的是,我实现所需行为的唯一方法是通过隐式转换。
  • 嗯,不,这不是唯一的方法。这就是你想要存在的简单方式。但是,还有其他方法,就是不容易。
【解决方案2】:

首先,我不确定将泛型与数字基元类型一起使用是否是一条好路。这是该语言的一个非常严重的缺陷,并且似乎没有任何计划在短时间内解决它。阅读this SO answer了解更多信息。

  1. 没有数字限制,你能做的最好的就是struct,这很糟糕。
  2. 对于任何算术支持,如果你正在实现一个矩阵,这是必须的,你需要用AddMultiply等定义一个IArithmetic接口,你将在整个过程中装箱和拆箱可能会对性能产生重大影响的地方。

    您的代码远比这差;因为您似乎缺少T 的通用接口,您需要转换为对象才能使通用转换成功。

    另外如果你没有类似if (typeof(T) == typeof(Complex)) ...的通用接口代码开始出现,这是使用泛型时的一个大红旗;泛型类/方法应该适用于 无限 种类型,而不仅仅是几个预设类型,这就是 generic 的含义。

我认为你应该退后一步,重新考虑你的方法。当语言类型系统似乎在与您作斗争并且没有任何帮助时,这肯定表明您做错了。

为什么不简单地实现一个最大类型的非泛型矩阵呢?复数矩阵。拥有浮点数或双精度矩阵有什么好处?它不可能是性能或内存效率,因为任何非通用解决方案都会比您当前的方法更好,所有的装箱和拆箱都在进行。

更新:在查看了您所基于的库后,我不确定您为什么不首先使用 Matrix&lt;T&gt;:作为基本类型。

public class DoubleMatrix : Matrix<double>
{
    //now this is legal
    public static implicit operator DoubleMatrix(FloatMatrix matrix)
        => matrix.ToDouble();
}

FloatMatrix 显然是FloatMatrix: Matrix&lt;Float&gt;

【讨论】:

  • 首先我不是从头开始实现的。这是 MathNet.Numerics 库的分支(我已链接到我要解决的问题)。老实说,这个库有很好的代码,但我真的需要这种隐式转换才能工作。 AFAIK 出于性能原因,它们允许浮点数等(与英特尔 MKL 等本地提供商合作)。
  • @PawelTroka 好吧,首先,这是您应该在问题中提出的相关信息。其次,你的演员阵容中的代码仍然是你的,从性能的角度来看,为了让你的代码编译而转换为 object 是可怕的。
  • 有时很难决定什么更重要。在性能方面,我猜我的实施建议中的选项 2 应该更快,但代码更丑(github.com/mathnet/mathnet-numerics/issues/304
  • 关于更新的答案 - 是的,这将起作用,但前提是我们使用从 Matrix 继承的类型。编译器仍然不会编译下面的代码(因为 Matrix 没有将其转换为 Matrix 的隐式运算符): var floatMatrix = Matrix.Build.Dense(Size, Size); var doubleMatrix = Matrix.Build.Dense(Size, Size); var 结果 = floatMatrixdoubleMatrix; CS0019 运算符 '' 不能应用于 'Matrix' 和 'Matrix' 类型的操作数 它也是“从基类转换”
  • 好的,我已经检查确定它不会工作,因为它是编译器错误“从基类转换”。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-11-15
  • 2017-09-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多