这个用于值转换委托的 Reflection.Emit 有什么问题吗?

问题描述 投票:0回答:1

很抱歉问了这个很长的问题,但我觉得我必须提供更多背景信息,因为我的问题非常具体。

更大的图片

我正在开发专门用于嵌入式 Linux 平台的 Unity 工具。

此环境有一定的限制,例如具体来说对运行时分配非常敏感(GC 导致卡顿等)-> 我们不会避免任何形式的运行时分配或更好地说取消分配。

我从哪里来

该工具的一个主要功能是类型转换。

原始的(POC)实现分解看起来有点像例如

public interface IValue
{
    bool AsInt(out int result);
    bool AsFloat(out float result);
    bool AsDouble(out double result);
    //...
    // explicit conversions for all supported value types
}

public abstract Value<T> : IValue
{
    [SerializeField] private T m_Value;

    public T TypedValue
    {
        get => m_Value;
        set => m_Value = value;
    }

    private bool TryCast<TTarget>(out TTarget result)
    {
        if (m_Value is TTarget target)
        {
            result = target;
            return true;
        }

        result = default;
        return false;
    }

    public virtual bool AsInt(out int result)
    {
        return TryCast(out result);
    }

    public virtual bool AsFloat(out float result)
    {
        return TryCast(out result);
    }

    public virtual bool AsDouble(out double result)
    {
        return TryCast(out result);
    }

    //...
}

作为实现示例(有更复杂的转换和许多类型 - 这只是为了理解概念)

[Serializable]
public class BoolOriginal : Value<bool>
{
    public override bool AsInt(out int result)
    {
        result = TypedValue ? 1 : 0;
        return true;
    }

    public override bool AsFloat(out float result)
    {
        result = TypedValue ? 1.0f : 0.0f;
        return true;
    }

    public override bool AsDouble(out double result)
    {
        result = TypedValue ? 1.0 : 0.0;
        return true;
    }
}

原始问题/限制

现在最大的“问题”是 - 一旦您想添加对新类型的支持,您就必须将其硬编码到接口中,至少是基类中。由于这个工具应该是一个只读的package,这是我们以后不能做的事情。

我的第一次“解决方案”尝试

所以我想我可以通过引入泛型并做类似的事情来解决这个问题

public interface IValue
{
    bool As<TTarget>(out TTarget result);
}

public abstract class Value<T> : IValue
{
    [SerializeField] private T m_Value;

    public T TypedValue
    {
        get => m_Value;
        set => m_Value = value;
    }

    public bool As<TTarget>(out TTarget result)
    {
        if (TypedValue is TTarget target)
        {
            result = target;
            return true;
        }

        if (Conversions.TryGetValue(typeof(TTarget), out var conversion))
        {
            result = (TTarget)conversion(TypedValue);
            return true;
        }

        if (k_DefaultConversions.TryGetValue(typeof(TTarget), out var defaultConversion))
        {
            result = (TTarget)defaultConversion(TypedValue);
            return true;
        }

        result = default;
        return false;
    }

    private static readonly IReadOnlyDictionary<Type, Func<T, object>> k_DefaultConversions = new Dictionary<Type, Func<T, object>>
    {
        { typeof(string), value => value.ToString() },
    };

    protected abstract IReadOnlyDictionary<Type, Func<T, object>> Conversions { get; }
}

然后简单地让继承者实现他们自己的转换

public class BoolPlain : Value<bool>
{
    private static readonly IReadOnlyDictionary<Type, Func<bool, object>> k_Conversions = new Dictionary<Type, Func<bool, object>>
    {
        { typeof(int), value => value ? 1 : 0 },
        { typeof(float), value => value ? 1.0f : 0.0f },
        { typeof(double), value => value ? 1.0 : 0.0 }
    };

    protected override IReadOnlyDictionary<Type, Func<bool, object>> Conversions => k_Conversions;
}

这工作正常 - 但引入了拳击分配!同样,这似乎不是什么大事,但在这个特定的嵌入式环境中它有点像!

那现在怎么办?

我现在正在研究两件事来解决这个问题

Linq 表达式

public abstract class Value<T> : IValue
{
    private T m_Value;

    public T TypedValue
    {
        get => m_Value;
        set => m_Value = value;
    }

    public bool As<TTarget>(out TTarget result)
    {
        if (TypedValue is TTarget target)
        {
            result = target;
            return true;
        }

        if (Conversions.TryGetValue(typeof(TTarget), out var conversion) && conversion is Func<T, TTarget> converter)
        {
            result = converter(TypedValue);
            return true;
        }

        if (k_DefaultConversions.TryGetValue(typeof(TTarget), out var defaultConversion) && defaultConversion is Func<T, TTarget> defaultConverter)
        {
            result = defaultConverter(TypedValue);
            return true;
        }

        result = default;
        return false;
    }

    protected static Delegate CreateConverter<TOutput>(Func<T, TOutput> converter)
    {
        var input = Expression.Parameter(typeof(T), "input");
        var body = Expression.Invoke(Expression.Constant(converter), input);
        var lambda = Expression.Lambda(body, input);
        return lambda.Compile();
    }

    private static readonly IReadOnlyDictionary<Type, Delegate> k_DefaultConversions = new Dictionary<Type, Delegate>
    {
        { typeof(string), CreateConverter<string>(v => v.ToString()) },
    };

    protected abstract IReadOnlyDictionary<Type, Delegate> Conversions { get; }
}

及实施

public class BoolValue : Value<bool>
{
    private static readonly IReadOnlyDictionary<Type, Delegate> k_Conversions = new Dictionary<Type, Delegate>
    {
        { typeof(int), CreateConverter<int>(v => v ? 1 : 0) },
        { typeof(float), CreateConverter<float>(v => v ? 1.0f : 0.0f) },
        { typeof(double), CreateConverter<double>(v => v ? 1.0 : 0.0) }
    };

    protected override IReadOnlyDictionary<Type, Delegate> Conversions => k_Conversions;
}

与上面的简单通用方法相比,这至少将分配量减半,并且看起来效率更高。

问题

因此,作为替代方案,我想尝试

Reflection.Emit
并执行以下操作(有一些 ChatGPT 和研究帮助不会撒谎 ^^)

private static readonly valueType = typeof(T);

protected static Delegate CreateConverter<TOutput>(Func<T, TOutput> converter)
{
    var outType = typeof(TOutput);

    var method = new DynamicMethod(
        "Convert_" + valueType.Name + "_To_" + outType.Name,
        outType,
        new[] { valueType },
        typeof(Value<>).Module,
        true);

    var il = method.GetILGenerator();
    il.Emit(OpCodes.Ldarg_0);
    il.EmitCall(OpCodes.Call, converter.Method, null);
    il.Emit(OpCodes.Ret);

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

但这总是给我一个

InvalidProgramException: Invalid IL code in (wrapper dynamic-method) object:Convert_Boolean_To_String (bool): IL_0001: call      0x00000001

我尝试了很多不同的迭代 - 也使用

OpCodes.Callvirt
代替 - 但这个问题仍然存在。

我在这里做错了什么?

typeof(Value<>).Module
中使用的基类是通用的,这可能是一个问题吗?

在这里摆弄


目前,我也对任何其他替代方案持开放态度,只要能在保持分配限制的同时提供所需的灵活性。

c# garbage-collection dynamic-memory-allocation reflection.emit linq-expressions
1个回答
0
投票

要调用委托,您需要调用其

Invoke
方法。这意味着您需要
converter
本身。您不能使用
.Method
属性,因为您不知道它是什么类型的方法(实例/静态),也不知道它是否因额外或缺失
this
而涉及任何改组。

因此,如果您真的想要使用此方法(不要,请参见下文),那么您需要传入原始的

Func
,例如:

protected static Delegate CreateConverter<TOutput>(Func<T, TOutput> converter)
{
    var outType = typeof(TOutput);

    var method = new DynamicMethod(
        "Convert_" + valueType.Name + "_To_" + outType.Name,
        outType,
        new[] { typeof(Func<T, TOutput>), valueType, },
        typeof(Value<>).Module,
        true);

    var il = method.GetILGenerator();
    il.Emit(OpCodes.Ldarg_0);
    il.Emit(OpCodes.Ldarg_0);
    il.EmitCall(OpCodes.Call, typeof(Func<T, TOutput>).GetMethod("Invoke"), null);
    il.Emit(OpCodes.Ret);

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

但显然这样做是没有意义的。如果您想要的只是将

Func<T, TOutput>
作为
Delegate
返回,则不需要动态方法,只需返回已有的委托即可。

所以只需使用您原来的委托即可。

// we expect each Delegate to be a Func<T, Type> where Type is the key in the dictionay
protected abstract IReadOnlyDictionary<Type, Delegate> Conversions { get; }

public bool As<TTarget>(out TTarget result)
{
    if (TypedValue is TTarget result)
    {
        return true;
    }

    if (Conversions.TryGetValue(typeof(TTarget), out var conversion) && conversion is Func<T, TTarget> converter)
    {
        result = converter(TypedValue);
        return true;
    }

    if (k_DefaultConversions.TryGetValue(typeof(TTarget), out var defaultConversion) && defaultConversion is Func<T, TTarget> defaultConverter)
    {
        result = defaultConverter(TypedValue);
        return true;
    }

    result = default;
    return false;
}

然后你可以像这样声明你的字典:

public class BoolPlain : Value<bool>
{
    private static readonly IReadOnlyDictionary<Type, Delegate> k_Conversions = new Dictionary<Type, Delegate>
    {
        { typeof(int),    (Func<bool, int>)value => value ? 1 : 0 },
        { typeof(float),  (Func<bool, float>)value => value ? 1.0f : 0.0f },
        { typeof(double), (Func<bool, double>)value => value ? 1.0 : 0.0 }
    };

    protected override IReadOnlyDictionary<Type, Func<bool, object>> Conversions => k_Conversions;
}
© www.soinside.com 2019 - 2024. All rights reserved.