1

我正在使用 MiscUtils 库(感谢 Marc G. 和 Jon S.)并试图向Sqrt它添加一个通用函数。这个问题可以很容易地重现:

class N<T>
{
    public N(T value)
    {
        Value = value;
    }

    public readonly T Value;

    public static implicit operator T(N<T> n)
    {
        return n.Value;
    }

    public static implicit operator N<T>(T value)
    {
        return new N<T>(value);
    }

    public static T operator /(N<T> lhs, T rhs)
    {
        // Operator.Divide is essentially a wrapper around 
        // System.Linq.Expressions.Expression.Divide
        return Operator.Divide(lhs.Value, rhs);
    }
}

// fails with: No coercion operator is defined 
// between types 'System.Double' and 'N`1[System.Single]'.
var n = new Numeric<float>(1f);
var x = Operator.DivideAlternative(n, 1.0);

// this works as the N<T> is first converted to a 
// float via the implicit conversion operator 
var result = n / 1.0;

现在,我意识到为什么会发生这种情况,但我还没有想出解决办法。作为参考,这里是当前的Sqrt实现。我几乎没有构建表达式树的经验。

public static double Sqrt<T>(T value)
{
    double oldGuess = -1;
    double guess = 1;
    while(Abs(guess - oldGuess) > 1)
    {
        oldGuess = guess;
        // the first evaluated call to DivideAlternative throws
        guess = Operator.Divide(
                    Operator.AddAlternative(guess, 
                        Operator.DivideAlternative(value, guess)),
                    2);
    }

    return guess;
}

编辑:好的,所以我自己解决了这个问题,但为了让问题尽可能简单,我显然走得太远了,花了太多时间回答困惑的人试图提供帮助的问题。

所以,这就是问题的全部。

我两班;一个执行转换,另一个执行图像数据(像素)的统计分析。让我们关注后者,因为问题是一样的:

abstract class ImageStatistics
{
    private readonly object _pixels;

    public ImageStatistics(object pixelArray)
    {
        Pixels = pixelArray;
    }

    // calculate the standard deviation of pixel values
    public double CalcStdDev();
}

像素数组可以是任何数字类型。在实践中,它将是floatintushortbyte。现在,因为泛型不能做这样的事情:

public T Add<T>(T lhs, T rhs)
{
    return lhs + rhs;  // oops, no operator + for T
}

如果不转换为正确的数组类型,我无法对像素值本身进行任何类型的统计分析。所以,我需要有 N 个子类ImageProcessor来支持 N 个像素类型。

嗯,这很糟糕。我很想拥有一个具有像素数据的通用ImageProcessor<T>类。T[]因此,我查看了允许这样做的 MiscUtils 库。

4

4 回答 4

1

Math.Sqrt需要一个双,那么为什么不只提供一个呢?

public static double Sqrt<T>(T value)
{
    return Math.Sqrt(Convert.ToDouble(value));
}

您也可以考虑强制转换为dynamic.

public static double Sqrt<T>(T value)
{   
    return Math.Sqrt((dynamic) value);
}

此技术也可用于加法等运算符:

public static T Add<T>(T a, T b)
{
    return (dynamic) a + (dynamic) b;
}
于 2013-07-25T03:52:33.447 回答
0

让我先说这可能不值得付出努力,考虑到如何维护这段代码。我在大约 10 分钟内写了这篇文章,所以不要指望有什么太壮观的东西。

// You'll need this
public interface ISquareRootHelper
{
    double Sqrt<T>(T value)
        where T : struct;
}

class Program
{
    private static ISquareRootHelper helper; 

    // Build the helper
    public static void BuildSqrtHelper()
    {
        // Let's use a guid for the assembly name, because guid!
        var assemblyName = new AssemblyName(Guid.NewGuid().ToString());

        // Blah, blah, boiler-plate dynamicXXX stuff
        var dynamicAssembly = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
        var dynamicModule = dynamicAssembly.DefineDynamicModule(assemblyName.Name);
        var dynamicType = dynamicModule.DefineType("SquareRootHelper");

        // Let's create our generic square root method in our dynamic type
        var sqrtMethod = dynamicType.DefineMethod("Sqrt", MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.Virtual);
        sqrtMethod.SetReturnType(typeof(double));

        // Well, I guess here is where we actually make the method generic
        var genericParam = sqrtMethod.DefineGenericParameters(new[] {"T"});
        genericParam[0].SetGenericParameterAttributes(GenericParameterAttributes.NotNullableValueTypeConstraint);

        // Add a generic parameter, and set it to override our interface method
        sqrtMethod.SetParameters(genericParam);
        dynamicType.DefineMethodOverride(sqrtMethod, typeof(ISquareRootHelper).GetMethod("Sqrt"));

        // Magic sauce!
        var ilGenerator = sqrtMethod.GetILGenerator();

        // Math.Sqrt((double)value);
        ilGenerator.Emit(OpCodes.Ldarg_1); // arg_0 is this*
        ilGenerator.Emit(OpCodes.Conv_R8);

        var mathSqrtMethodInfo = typeof(Math).GetMethod("Sqrt");
        ilGenerator.EmitCall(OpCodes.Call, mathSqrtMethodInfo, null);

        ilGenerator.Emit(OpCodes.Ret);

        // Since we're overriding the interface method, we need to have the type
        // implement the interface
        dynamicType.AddInterfaceImplementation(typeof(ISquareRootHelper));

        // Create an instance of the class
        var sqrtHelperType = dynamicType.CreateType();
        helper = (ISquareRootHelper)Activator.CreateInstance(sqrtHelperType);
    }

    public static void Main(string[] args)
    {
        BuildSqrtHelper();

        Console.WriteLine(helper.Sqrt((short)64));    // Works!
        Console.WriteLine(helper.Sqrt((ushort)64));   // Works!
        Console.WriteLine(helper.Sqrt((int)64));      // Works!
        Console.WriteLine(helper.Sqrt((uint)64));     // Works*!
        Console.WriteLine(helper.Sqrt((byte)64));     // Works!
        Console.WriteLine(helper.Sqrt((sbyte)64));    // Works!
        Console.WriteLine(helper.Sqrt((float)64));    // Works!
        Console.WriteLine(helper.Sqrt((double)64));   // Works!
        Console.WriteLine(helper.Sqrt((long)64));     // Works!
        Console.WriteLine(helper.Sqrt((ulong)64));    // Works*!

        // Let's try non-primitives!
        Console.WriteLine(helper.Sqrt(DateTime.Now)); // Doesn't fail, but doesn't actually work
        Console.WriteLine(helper.Sqrt(Guid.NewGuid())); // InvalidProgramException!
    }
}

无论如何,我想这证明它可以做到。只要确保在使用它时,只传入原始类型,否则所有失败都会崩溃。实际上,只有当您传入一个大于 8 字节的结构时,它才会抛出异常,因为这会使堆栈不平衡。但是,您不能像sizeof(T)在该方法中那样进行检查,因为它会在 JITing 过程中失败。

*此外,上面的某些类型旁边还有一些s。编译器和/或当您传入无符号数与有符号数时会完成一些额外的逻辑Math.Sqrt,以及这与负数的关系。例如:

Console.WriteLine(Math.Sqrt(unchecked((uint)-2)));   // 65535.9999847412
Console.WriteLine(helper.Sqrt(unchecked((uint)-2))); // NaN :(

不过,您可以改进它并检查上面的内容。另外,我不推荐这种解决方案,特别是如果您对 IL 不满意。另外,这可能比编写一堆不同的方法来处理您想要的操作更冗长和复杂。

于 2013-07-25T01:22:58.317 回答
0

控制台应用程序创建对象数组(未知类型)并计算方形路线(双精度)

using System;

namespace GenericSqrt
{
    class Program
    {
        static void Main(string[] args)
        {
            var array = new object[] { "2", null, 4.1f, 4.444D, "11.3", 0, "Text", new DateTime(1, 1, 1) };

            foreach (var value in array)
            {
                try
                {
                    Console.WriteLine(Sqrt(value));
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }
            }
            Console.ReadLine();
        }

        private static double Sqrt(object value)
        {
            double converterValue = Convert.ToDouble(value);
            return Math.Sqrt(converterValue);
        }
    }
}

输出如下所示:

1.4142135623731
0
2.02484564958235
2.10807969488822
3.36154726279432
0
Input string was not in a correct format.
Invalid cast from 'DateTime' to 'Double'.

如您所说,如果 type 确实是任何数字类型,则没有问题要解决。

于 2013-07-24T22:56:41.980 回答
-1

这可行,但有点难看:

public static implicit operator Numeric<T>(double value)
{
    return new Numeric<T>((T)Convert.ChangeType(value, typeof(T)));
}

public static implicit operator double(Numeric<T> n)
{
    return Convert.ToDouble(n.Value);           
}

对于每种支持的类型都必须重复它,这使得它的通用性大大降低。我IConvertible在那儿打了一个限制,以防万一。如果有人有更好的解决方案,我会全力以赴。

于 2013-07-24T21:49:07.950 回答