简体   繁体   中英

Can auto property backing field names be relied upon?

Short version: Can I rely on auto property backing fields having names in a certain format? Like " <my_member>k__BackingField "?

I am writing some serialisation code, which uses reflection to figure out which fields/properties to serialise.

Currently it serialises auto properties using Type.GetMembers , then using PropertyInfo.GetGetMethod and PropertyInfo.GetSetMethod on property members, checking that they have the CompilerGeneratedAttribute (to make sure they are auto), then invoking those methods to get and set the underlying value.

This is usually fine, but it fails for eg C# 6 "read-only auto-properties", which lack a setter, but which should conceptually work ok because I do support serialising read-only fields.

So I wonder if I should just find the backing field for auto properties and serialise that instead - but I can't find any API to get a PropertyInfo 's corresponding backing field - instead I just see that there happens to be a PropertyInfo , and then a FieldInfo with a name like " <my_member>k__BackingField " - but I don't know if I can rely on that, or if it's something that may change between compilations/compiler versions.

No.

The language standard do not restrict the auto generated backing field's name of auto-properties.

Here's my way to get the FieldInfo of auto property's backing field:

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;

The last 6 single line methods' code looks maybe a little messed since the browser uses a non-fixed width font.

The 2 strict check methods are adapted to M$ dotnetfx runtimes.

The key code use MSIL bytes generated by compiler to find the auto property's backing field in last 4 methods. They works for M$'s dotnetfx4x and dotnet5, and maybe all M$'s dotnetfx runtimes.

If you use it with mono or other frameworks, you can peek the compiler emitted auto property's attributes, backing field's name and setter/getter's IL bytes with dnSpy or other similar tools and then modify the 6 single line methods to adapt them. And of course you can add some other strict checks to ensure the code works correct on the fx which your program is running on.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM