我正在使用MiscUtils库(谢谢Marc .和Jon .)并试图向其添加一个通用的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;
}编辑:好的,所以我自己解决了这个问题,但是为了尽量保持这个问题的简单,我显然走得太远了,花了太多的时间回答困惑的人想要帮助我的问题。
所以,这就是问题的全部。
I两个类;一个执行转换,另一个执行图像数据(像素)的统计分析。让我们关注后者,因为问题是相同的:
abstract class ImageStatistics
{
private readonly object _pixels;
public ImageStatistics(object pixelArray)
{
Pixels = pixelArray;
}
// calculate the standard deviation of pixel values
public double CalcStdDev();
}像素数组可以是任意数字类型。实际上,它将是float、int、ushort或byte。现在,因为泛型不能做这样的事情:
public T Add<T>(T lhs, T rhs)
{
return lhs + rhs; // oops, no operator + for T
}如果不转换成适当的数组类型,我就无法对像素值本身执行任何类型的统计分析。因此,我需要N个ImageProcessor子类来支持N个像素类型.
那太糟糕了。我希望有一个通用的ImageProcessor<T>类,它有一个像素数据的T[]。因此,我查看了MiscUtils库,它只允许这样做。
发布于 2013-07-25 03:52:33
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-24 22:56:41
控制台应用程序创建对象数组(未知类型)和计算正方形路由(双)
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'.如果类型确实是数字类型中的任何一种,正如您所说的那样,没有任何问题需要解决。
发布于 2013-07-25 01:22:58
首先,我要说,考虑到如何维护这段代码,它可能不值得付出这么大的努力。我只花了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感到不舒服的时候。此外,这可能比仅仅编写一组不同的方法来处理您想要的操作要复杂得多。
https://stackoverflow.com/questions/17822444
复制相似问题