可以依赖自动属性支持字段名称吗?

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

简短版本:我可以依赖具有特定格式名称的自动属性支持字段吗?喜欢“

<my_member>k__BackingField
”吗?

我正在编写一些序列化代码,它使用反射来确定要序列化的字段/属性。

目前,它使用

Type.GetMembers
序列化自动属性,然后在属性成员上使用
PropertyInfo.GetGetMethod
PropertyInfo.GetSetMethod
,检查它们是否具有
CompilerGeneratedAttribute
(以确保它们是自动的),然后调用这些方法来获取和设置潜在价值。

这通常很好,但对于例如以下情况会失败。 C# 6“只读自动属性”,缺少 setter,但从概念上讲应该可以正常工作,因为我确实支持序列化只读字段。

所以我想知道我是否应该找到自动属性的支持字段并序列化它 - 但我找不到任何 API 来获取

PropertyInfo
的相应支持字段 - 相反,我只是看到恰好有一个
PropertyInfo
,然后是一个
FieldInfo
,其名称类似于“
<my_member>k__BackingField
” - 但我不知道我是否可以依赖它,或者它是否可能在编译/编译器版本之间发生变化。

c# reflection
2个回答
0
投票

没有。

语言标准不限制自动生成的支持字段的自动属性名称。

这是我获取 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 上正确工作。


0
投票

我会反其道而行之,我相信您可以依赖始终遵循相同命名格式的支持字段。 至少对于当前的编译器 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();
}
© www.soinside.com 2019 - 2024. All rights reserved.