我正在使用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();
}
像素数组可以是任何数字类型。实际上,它可以是float
,int
,ushort
或byte
。现在,因为泛型不能做这样的事情:
public T Add<T>(T lhs, T rhs)
{
return lhs + rhs; // oops, no operator + for T
}
我不能对像素值本身进行任何形式的统计分析,而不会转换为正确的数组类型。所以,我需要有ImageProcessor
的N个子类来支持N个像素类型。
ImageProcessor<T>
类,其中包含T[]
个像素数据。所以,我查看了MiscUtils库,它允许这样做。
答案 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
约束。如果有人有更好的解决方案,我会全力以赴。