【问题标题】:Can auto property backing field names be relied upon?可以依赖自动属性支持字段名称吗?
【发布时间】:2020-06-07 14:59:14
【问题描述】:

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

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

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

这通常很好,但它会失败,例如C# 6“只读自动属性”,它缺少一个设置器,但在概念上应该可以正常工作,因为我确实支持序列化只读字段。

所以我想知道我是否应该只找到自动属性的支持字段并将其序列化 - 但我找不到任何 API 来获取 PropertyInfo 的相应支持字段 - 相反,我只是看到恰好有成为PropertyInfo,然后是名称为“<my_member>k__BackingField”的FieldInfo - 但我不知道我是否可以依赖它,或者它是否可能会在编译/编译器版本之间发生变化。

【问题讨论】:

  • 您不能依赖命名在不同版本之间保持一致。您希望如何序列化只读属性?仅使用带有 setter 的属性进行序列化是有意义的
  • 您可以使用 GetMembers(BindiingFlags.Instance | BinidngFlags.NonPublic) 找到所有这些
  • @a-ctor:谢谢!我不认为您可以向我指出任何提到命名的文档?我真的找不到任何东西(尽管这本身就是证据!)。我序列化只读字段,因为这可以通过反射实现,所以我希望通过序列化只读支持字段来序列化只读属性,就像我做一个普通字段一样(所以不使用 setter 和 getter)。 sTrenat - 谢谢!我已经这样做了(因此我是如何找到支持字段名称的),我只需要知道我是否可以依赖支持字段名称保持一致,或者我是否永远不会看到它......
  • @BenHymers 您可以在 roslyn 文档中搜索,但我认为您的搜索结果为空。您不应该在构造函数之外设置只读字段,因为编译器可以基于只读进行优化。我会满足使用string Prop { get; private set; } 的要求,这样您就可以安全地设置字段。
  • @a-ctor: 再次感谢 :) 我已经搜索了更多,现在我有更好的词要搜索,而且似乎名称是实现定义的并且不可靠,所以我会放弃这个追求。感谢您提供有关只读字段的额外信息 - 听起来这也在“实现定义”类别中!

标签: c# reflection


【解决方案1】:

没有。

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

这是我获取自动属性支持字段的 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 或其他类似工具查看编译器发出的自动属性的属性、支持字段的名称和 setter/getter 的 IL 字节,然后修改 6 个单行方法以适应它们。当然,您可以添加一些其他严格的检查,以确保代码在您的程序正在运行的 fx 上正常工作。

【讨论】:

    猜你喜欢
    • 2019-02-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-06
    • 1970-01-01
    • 2012-02-07
    • 2012-12-04
    • 2011-08-29
    相关资源
    最近更新 更多