gpt4 book ai didi

.net - 如何在不装箱的情况下将泛型类型 T 的值转换为 double?

转载 作者:行者123 更新时间:2023-12-03 20:48:10 25 4
gpt4 key购买 nike

想象一下下面的简单代码:

public void F<T>(IList<T> values) where T : struct
{
foreach (T value in values)
{
double result;
if (TryConvertToDouble((object)value, out result))
{
ConsumeValue(result);
}
}
}

public void ConsumeValue(double value)
{
}

上面代码的问题是强制转换为对象,这会导致循环中的装箱。

有没有办法实现相同的功能,即在不诉诸于 foreach 循环中的装箱的情况下为 ConsumeValue 提供所有值?请注意, F 必须是泛型方法。

只要在循环外执行一次,我就可以忍受昂贵的准备代码。例如,如果需要发出一个花哨的动态方法,那么只需执行一次就可以了。

编辑

T 保证是某种数字类型或 bool 值。

动机。想象一下元数据驱动的应用程序,其中代理报告数据流,其中数据项类型根据数据流元数据动态发出。还想象一下,有一个归一化引擎,它知道根据某种算法对数字数据流进行归一化。传入数字数据流的类型仅在运行时已知,并且可以定向到该数据类型的通用方法。然而,规范化器期望 double 并产生 double 。这是一个非常高级的描述,请不要深入研究。

编辑 2

关于 Actor 阵容翻倍。实际上,我们有一种方法可以使用以下签名转换为 double:
bool TryConvertToDouble(object value, out double result);

我应该首先在示例中使用它,但我想节省空间并编写一些不起作用的东西。现在修好了。谢谢你的注意。

编辑 3

当前的实现确实将值装箱。即使我没有分析器对其性能损失的判断(如果有的话),我仍然很想知道是否有没有装箱(并且没有转换为字符串)的解决方案。让我称之为纯粹的学术兴趣。

这真的让我感兴趣,因为这样的事情在带有模板的 C++ 中是微不足道的,但是,当然,我不会再开始争论什么是更好的 .NET 泛型或 C++ 模板。

编辑 4

感谢 https://stackoverflow.com/users/267/lasse-v-karlsen谁提供了答案。实际上,我已经使用他的代码示例编写了一个这样的简单类:
public static class Utils<T>
{
private static class ToDoubleConverterHolder
{
internal static Func<T, double> Value = EmitConverter();

private static Func<T, double> EmitConverter()
{
ThrowIfNotConvertableToDouble(typeof(T));

var method = new DynamicMethod(string.Empty, typeof(double), TypeArray<T>.Value);
var il = method.GetILGenerator();

il.Emit(OpCodes.Ldarg_0);
if (typeof(T) != typeof(double))
{
il.Emit(OpCodes.Conv_R8);
}
il.Emit(OpCodes.Ret);

return (Func<T, double>)method.CreateDelegate(typeof(Func<T, double>));
}
}

public static double ConvertToDouble(T value)
{
return ToDoubleConverterHolder.Value(value);
}
}

在哪里:
  • ThrowIfNotConvertableToDouble(Type)是一种简单的方法,可确保给定类型可以转换为 double,即某些数字类型或 bool。
  • TypeArray<T>是生成 new[]{ typeof(T) } 的辅助类
  • Utils<T>.ConvertToDouble方法以最有效的方式将任何数值转换为 double,如此问题的答案所示。

    最佳答案

    注意:我的基于实例的代码生成的初始代码中存在一个错误。请重新检查下面的代码。更改的部分是将值加载到堆栈上的顺序(即 .Emit 行)。答案和存储库中的代码都已修复。
    如果你想走代码生成的路线,正如你在问题中所暗示的那样,这里是示例代码:
    它在一个整数数组和一个 bool 数组上执行 ConsumeValue(在我的示例中什么都不做)1000 万次,对执行进行计时(它运行所有代码一次,以消除 JIT 开销,以免造成时间偏差。)
    输出:

    F1 ints = 445ms         <-- uses Convert.ToDouble
    F1 bools = 351ms
    F2 ints = 159ms <-- generates code on each call
    F2 bools = 167ms
    F3 ints = 158ms <-- caches generated code between calls
    F3 bools = 163ms
    代码生成的开销减少了大约 65%。
    编码:
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;
    using System.Reflection;
    using System.Reflection.Emit;

    namespace ConsoleApplication15
    {
    class Program
    {
    public static void F1<T>(IList<T> values) where T : struct
    {
    foreach (T value in values)
    ConsumeValue(Convert.ToDouble(value));
    }

    public static Action<T> GenerateAction<T>()
    {
    DynamicMethod method = new DynamicMethod(
    "action", MethodAttributes.Public | MethodAttributes.Static,
    CallingConventions.Standard,
    typeof(void), new Type[] { typeof(T) }, typeof(Program).Module,
    false);
    ILGenerator il = method.GetILGenerator();

    il.Emit(OpCodes.Ldarg_0); // get value passed to action
    il.Emit(OpCodes.Conv_R8);
    il.Emit(OpCodes.Call, typeof(Program).GetMethod("ConsumeValue"));
    il.Emit(OpCodes.Ret);

    return (Action<T>)method.CreateDelegate(typeof(Action<T>));
    }

    public static void F2<T>(IList<T> values) where T : struct
    {
    Action<T> action = GenerateAction<T>();
    foreach (T value in values)
    action(value);
    }

    private static Dictionary<Type, object> _Actions =
    new Dictionary<Type, object>();
    public static void F3<T>(IList<T> values) where T : struct
    {
    Object actionObject;
    if (!_Actions.TryGetValue(typeof(T), out actionObject))
    {
    actionObject = GenerateAction<T>();
    _Actions[typeof (T)] = actionObject;
    }
    Action<T> action = (Action<T>)actionObject;
    foreach (T value in values)
    action(value);
    }

    public static void ConsumeValue(double value)
    {
    }

    static void Main(string[] args)
    {
    Stopwatch sw = new Stopwatch();

    int[] ints = Enumerable.Range(1, 10000000).ToArray();
    bool[] bools = ints.Select(i => i % 2 == 0).ToArray();

    for (int pass = 1; pass <= 2; pass++)
    {
    sw.Reset();
    sw.Start();
    F1(ints);
    sw.Stop();
    if (pass == 2)
    Console.Out.WriteLine("F1 ints = "
    + sw.ElapsedMilliseconds + "ms");

    sw.Reset();
    sw.Start();
    F1(bools);
    sw.Stop();
    if (pass == 2)
    Console.Out.WriteLine("F1 bools = "
    + sw.ElapsedMilliseconds + "ms");

    sw.Reset();
    sw.Start();
    F2(ints);
    sw.Stop();
    if (pass == 2)
    Console.Out.WriteLine("F2 ints = "
    + sw.ElapsedMilliseconds + "ms");

    sw.Reset();
    sw.Start();
    F2(bools);
    sw.Stop();
    if (pass == 2)
    Console.Out.WriteLine("F2 bools = "
    + sw.ElapsedMilliseconds + "ms");

    sw.Reset();
    sw.Start();
    F3(ints);
    sw.Stop();
    if (pass == 2)
    Console.Out.WriteLine("F3 ints = "
    + sw.ElapsedMilliseconds + "ms");

    sw.Reset();
    sw.Start();
    F3(bools);
    sw.Stop();
    if (pass == 2)
    Console.Out.WriteLine("F3 bools = "
    + sw.ElapsedMilliseconds + "ms");
    }
    }
    }
    }
    请注意,如果您将 GenerationAction、F2/3 和 ConsumeValue 设为非静态,则必须稍微更改代码:
  • 全部 Action<T>声明变成 Action<Program, T>
  • 更改 DynamicMethod 的创建以包含“this”参数:
     DynamicMethod method = new DynamicMethod(
    "action", MethodAttributes.Public | MethodAttributes.Static,
    CallingConventions.Standard,
    typeof(void), new Type[] { typeof(Program), typeof(T) },
    typeof(Program).Module,
    false);
  • 更改指令以在正确的时间加载正确的值:
     il.Emit(OpCodes.Ldarg_0); // get "this"
    il.Emit(OpCodes.Ldarg_1); // get value passed to action
    il.Emit(OpCodes.Conv_R8);
    il.Emit(OpCodes.Call, typeof(Program).GetMethod("ConsumeValue"));
    il.Emit(OpCodes.Ret);
  • 每当调用时将“this”传递给操作:
     action(this, value);

  • 这是非静态方法的完整更改程序:
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;
    using System.Reflection;
    using System.Reflection.Emit;

    namespace ConsoleApplication15
    {
    class Program
    {
    public void F1<T>(IList<T> values) where T : struct
    {
    foreach (T value in values)
    ConsumeValue(Convert.ToDouble(value));
    }

    public Action<Program, T> GenerateAction<T>()
    {
    DynamicMethod method = new DynamicMethod(
    "action", MethodAttributes.Public | MethodAttributes.Static,
    CallingConventions.Standard,
    typeof(void), new Type[] { typeof(Program), typeof(T) },
    typeof(Program).Module,
    false);
    ILGenerator il = method.GetILGenerator();

    il.Emit(OpCodes.Ldarg_0); // get "this"
    il.Emit(OpCodes.Ldarg_1); // get value passed to action
    il.Emit(OpCodes.Conv_R8);
    il.Emit(OpCodes.Call, typeof(Program).GetMethod("ConsumeValue"));
    il.Emit(OpCodes.Ret);

    return (Action<Program, T>)method.CreateDelegate(
    typeof(Action<Program, T>));
    }

    public void F2<T>(IList<T> values) where T : struct
    {
    Action<Program, T> action = GenerateAction<T>();
    foreach (T value in values)
    action(this, value);
    }

    private static Dictionary<Type, object> _Actions =
    new Dictionary<Type, object>();
    public void F3<T>(IList<T> values) where T : struct
    {
    Object actionObject;
    if (!_Actions.TryGetValue(typeof(T), out actionObject))
    {
    actionObject = GenerateAction<T>();
    _Actions[typeof (T)] = actionObject;
    }
    Action<Program, T> action = (Action<Program, T>)actionObject;
    foreach (T value in values)
    action(this, value);
    }

    public void ConsumeValue(double value)
    {
    }

    static void Main(string[] args)
    {
    Stopwatch sw = new Stopwatch();

    Program p = new Program();
    int[] ints = Enumerable.Range(1, 10000000).ToArray();
    bool[] bools = ints.Select(i => i % 2 == 0).ToArray();

    for (int pass = 1; pass <= 2; pass++)
    {
    sw.Reset();
    sw.Start();
    p.F1(ints);
    sw.Stop();
    if (pass == 2)
    Console.Out.WriteLine("F1 ints = "
    + sw.ElapsedMilliseconds + "ms");

    sw.Reset();
    sw.Start();
    p.F1(bools);
    sw.Stop();
    if (pass == 2)
    Console.Out.WriteLine("F1 bools = "
    + sw.ElapsedMilliseconds + "ms");

    sw.Reset();
    sw.Start();
    p.F2(ints);
    sw.Stop();
    if (pass == 2)
    Console.Out.WriteLine("F2 ints = "
    + sw.ElapsedMilliseconds + "ms");

    sw.Reset();
    sw.Start();
    p.F2(bools);
    sw.Stop();
    if (pass == 2)
    Console.Out.WriteLine("F2 bools = "
    + sw.ElapsedMilliseconds + "ms");

    sw.Reset();
    sw.Start();
    p.F3(ints);
    sw.Stop();
    if (pass == 2)
    Console.Out.WriteLine("F3 ints = "
    + sw.ElapsedMilliseconds + "ms");

    sw.Reset();
    sw.Start();
    p.F3(bools);
    sw.Stop();
    if (pass == 2)
    Console.Out.WriteLine("F3 bools = "
    + sw.ElapsedMilliseconds + "ms");
    }
    }
    }
    }

    关于.net - 如何在不装箱的情况下将泛型类型 T 的值转换为 double?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3343551/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com