抱歉,这个问题很长,但我觉得我必须提供更多的背景,因为我的问题非常具体.
大局
我正在开发一个统一的工具,专门用于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
在这一点上,我也对任何其他在保持分配限制的同时提供所需灵活性的替代方案持开放态度.