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

大局

我正在开发一个统一的工具,专门用于Embedded Linux platforms.

这个环境有一定的限制,比如特别是对运行时分配非常敏感(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这是我们不能做的事情以后.

我的第一次"解决方案"try

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

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;
}

这个不错--但是引入了boxing allocations个!同样,这看起来并不是什么大事,但在这个特定的嵌入式环境中,它确实是一件大事!

现在怎么办?

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

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;
}

与上面的普通方法相比,这至少减少了一半的分配,并且似乎更有效率.

问题

因此,作为替代,我想try 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

我try 了很多不同的迭代—也使用了OpCodes.Callvirt—但这个问题仍然存在.

What am I doing wrong here?

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

100


在这一点上,我也对任何其他在保持分配限制的同时提供所需灵活性的替代方案持开放态度.

推荐答案

要调用委托,需要调用其Invoke方法.这意味着你需要converter本身.您不能使用.Method属性,因为您不知道它是什么类型的方法(实例/静态),也不知道它是否会因为多余的this或缺少的this而涉及任何混洗.

因此,如果你really想使用这个方法(不要,见下文),那么你需要传入原始的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_1);
    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),    new Func<bool, int>   (value => value ? 1 : 0) },
        { typeof(float),  new Func<bool, float> (value => value ? 1.0f : 0.0f) },
        { typeof(double), new Func<bool, double>(value => value ? 1.0 : 0.0) }
    };

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

dotnetfiddle

Csharp相关问答推荐

什么是通过反射创建类的泛型接口方法的正确方法?

SignalR客户端不会打印队列位置'

System.Text.Json数据化的C#类映射

图形API基于appid列表检索多个应用程序

创建临时Collection 最有效的方法是什么?堆栈分配和集合表达式之间的区别?

自动映射程序在GroupBy之后使用项目

获取具有AutoFaces的所有IOptions对象的集合

从.Net 6 DLL注册和检索COM对象(Typelib导出:类型库未注册.(异常来自HRESULT:0x80131165))

在swagger示例中添加默认数组列表

如果设置了另一个属性,则Newtonsoft JSON忽略属性

在使用AWS SDK for.NET时,如何判断S3是否已验证我的对象的校验和?

未在Windows上运行的Maui项目

匿名类型的AbstractValidator

错误:此版本的Visual Studio无法打开以下项目

从GRPC连接创建ZipArchive

DropDownListFor未显示选定值

ASP.NET核心MVC|如何在控制器方法之间传递值

外部应用&&的LINQ;左外部连接&类似于PostgreSQL的查询

将文本从剪贴板粘贴到RichTextBox时,新文本不会在RichTextBox ForeColor中着色

部署Aspnet Blazor服务器时出现未处理的Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware[1]异常