很抱歉问了这个很长的问题,但我觉得我必须提供更多背景信息,因为我的问题非常具体。
我正在开发专门用于嵌入式 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;
}
这工作正常 - 但引入了拳击分配!同样,这似乎不是什么大事,但在这个特定的嵌入式环境中它有点像!
我现在正在研究两件事来解决这个问题
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
中使用的基类是通用的,这可能是一个问题吗?
目前,我也对任何其他替代方案持开放态度,只要能在保持分配限制的同时提供所需的灵活性。
要调用委托,您需要调用其
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;
}