通用Sqrt实现

时间:2013-07-23 22:59:56

标签: c# generics miscutils

我正在使用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
}

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

嗯,那很糟糕。我希望有一个通用的ImageProcessor<T>类,其中包含T[]个像素数据。所以,我查看了MiscUtils库,它允许这样做。

4 个答案:

答案 0 :(得分: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;
}

答案 1 :(得分: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'.

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

答案 2 :(得分: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过程中失败。

此外,某些类型旁边还有一些*个。当你传递无符号数和有符号数时,编译器和/或Math.Sqrt有一些额外的逻辑,以及这与负数有什么关系。例如:

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

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

答案 3 :(得分:-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约束。如果有人有更好的解决方案,我会全力以赴。