简短版本:我可以依赖具有特定格式名称的自动属性支持字段吗?喜欢“
<my_member>k__BackingField
”吗?
我正在编写一些序列化代码,它使用反射来确定要序列化的字段/属性。
目前,它使用
Type.GetMembers
序列化自动属性,然后在属性成员上使用 PropertyInfo.GetGetMethod
和 PropertyInfo.GetSetMethod
,检查它们是否具有 CompilerGeneratedAttribute
(以确保它们是自动的),然后调用这些方法来获取和设置潜在价值。
这通常很好,但对于例如以下情况会失败。 C# 6“只读自动属性”,缺少 setter,但从概念上讲应该可以正常工作,因为我确实支持序列化只读字段。
所以我想知道我是否应该找到自动属性的支持字段并序列化它 - 但我找不到任何 API 来获取
PropertyInfo
的相应支持字段 - 相反,我只是看到恰好有一个PropertyInfo
,然后是一个 FieldInfo
,其名称类似于“<my_member>k__BackingField
” - 但我不知道我是否可以依赖它,或者它是否可能在编译/编译器版本之间发生变化。
没有。
语言标准不限制自动生成的支持字段的自动属性名称。
这是我获取 auto 属性的支持字段 FieldInfo 的方法:
public static FieldInfo? GetAutoPropertyBackingField(this PropertyInfo pi, bool strictCheckIsAutoProperty = false)
{
if (strictCheckIsAutoProperty && !StrictCheckIsAutoProperty(pi)) return null;
var gts = pi.DeclaringType?.GetGenericArguments();
var accessor = pi.GetGetMethod(true);
var msilBytes = accessor?.GetMethodBody()?.GetILAsByteArray();
var rtk = null != msilBytes
? accessor!.IsStatic
? GetAutoPropertyBakingFieldMetadataTokenInGetMethodOfStatic(msilBytes)
: GetAutoPropertyBakingFieldMetadataTokenInGetMethodOfInstance(msilBytes)
: -1;
accessor = pi.GetSetMethod(true);
msilBytes = accessor?.GetMethodBody()?.GetILAsByteArray();
if (null != msilBytes)
{
var wtk = accessor!.IsStatic
? GetAutoPropertyBakingFieldMetadataTokenInSetMethodOfStatic (msilBytes)
: GetAutoPropertyBakingFieldMetadataTokenInSetMethodOfInstance(msilBytes);
if (-1 != wtk)
{
if (wtk == rtk)
{
var wfi = pi.Module.ResolveField(wtk, gts, null);
if (!strictCheckIsAutoProperty || null == wfi || StrictCheckIsAutoPropertyBackingField(pi, wfi)) return wfi;
}
return null;
}
}
if (-1 == rtk) return null;
var rfi = pi.Module.ResolveField(rtk, gts, null);
return !strictCheckIsAutoProperty || null == rfi || StrictCheckIsAutoPropertyBackingField(pi, rfi) ? rfi : null;
}
private static bool StrictCheckIsAutoProperty (PropertyInfo pi) => null != pi.GetCustomAttribute<CompilerGeneratedAttribute>();
private static bool StrictCheckIsAutoPropertyBackingField(PropertyInfo pi, FieldInfo fi) => fi.Name == "<" + pi.Name + ">k__BackingField";
private static int GetAutoPropertyBakingFieldMetadataTokenInGetMethodOfStatic (byte[] msilBytes) => 6 == msilBytes.Length && 0x7E == msilBytes[0] && 0x2A == msilBytes[5] ? BitConverter.ToInt32(msilBytes, 1) : -1;
private static int GetAutoPropertyBakingFieldMetadataTokenInSetMethodOfStatic (byte[] msilBytes) => 7 == msilBytes.Length && 0x02 == msilBytes[0] && 0x80 == msilBytes[1] && 0x2A == msilBytes[6] ? BitConverter.ToInt32(msilBytes, 2) : -1;
private static int GetAutoPropertyBakingFieldMetadataTokenInGetMethodOfInstance(byte[] msilBytes) => 7 == msilBytes.Length && 0x02 == msilBytes[0] && 0x7B == msilBytes[1] && 0x2A == msilBytes[6] ? BitConverter.ToInt32(msilBytes, 2) : -1;
private static int GetAutoPropertyBakingFieldMetadataTokenInSetMethodOfInstance(byte[] msilBytes) => 8 == msilBytes.Length && 0x02 == msilBytes[0] && 0x03 == msilBytes[1] && 0x7D == msilBytes[2] && 0x2A == msilBytes[7] ? BitConverter.ToInt32(msilBytes, 3) : -1;
最后 6 个单行方法的代码看起来可能有点混乱,因为浏览器使用非固定宽度的字体。
这2种严格的检查方法适用于M$ dotnetfx运行时。
关键代码使用编译器生成的 MSIL 字节来查找最后 4 个方法中 auto 属性的支持字段。它们适用于 M$ 的 dotnetfx4x 和 dotnet5,也许还适用于所有 M$ 的 dotnetfx 运行时。
如果您将其与 Mono 或其他框架一起使用,您可以使用 dnSpy 或其他类似工具查看编译器发出的 auto 属性的属性、支持字段的名称和 setter/getter 的 IL 字节,然后修改 6 个单行方法以适应它们。当然,您可以添加一些其他严格的检查,以确保代码在您的程序运行的 fx 上正确工作。
我会反其道而行之,我相信您可以依赖始终遵循相同命名格式的支持字段。 至少对于当前的编译器 Roslyn 来说是这样,所以只要您不使用较旧的编译器,或者创建一个针对旧版本 C# 的库,我会说是的。
internal static string MakeBackingFieldName(string propertyName)
{
Debug.Assert((char)GeneratedNameKind.AutoPropertyBackingField == 'k');
return "<" + propertyName + ">k__BackingField";
}
唯一不同的时间
propertyName
是涉及接口时。
这是生成
propertyName
的代码,考虑到了接口。不过看起来可能很快就会改变。
name
参数将始终与标识符匹配。
public static string GetMemberName(string name, TypeSymbol explicitInterfaceTypeOpt, string aliasQualifierOpt)
{
if ((object)explicitInterfaceTypeOpt == null)
{
return name;
}
// TODO: Revisit how explicit interface implementations are named.
// CONSIDER (vladres): we should never generate identical names for different methods.
string interfaceName = explicitInterfaceTypeOpt.ToDisplayString(SymbolDisplayFormat.ExplicitInterfaceImplementationFormat);
PooledStringBuilder pooled = PooledStringBuilder.GetInstance();
StringBuilder builder = pooled.Builder;
if (!string.IsNullOrEmpty(aliasQualifierOpt))
{
builder.Append(aliasQualifierOpt);
builder.Append("::");
}
foreach (char ch in interfaceName)
{
// trim spaces to match metadata name (more closely - could still be truncated)
if (ch != ' ')
{
builder.Append(ch);
}
}
builder.Append(".");
builder.Append(name);
return pooled.ToStringAndFree();
}