是否可以访问自动实现属性背后的支持字段?

And*_*ein 55 c#

我知道我可以使用详细的属性语法:

private string _postalCode;

public string PostalCode
{
    get { return _postalCode; }
    set { _postalCode = value; }
}
Run Code Online (Sandbox Code Playgroud)

或者我可以使用自动实现的属性.

public string PostalCode { get; set; }
Run Code Online (Sandbox Code Playgroud)

我可以以某种方式访问​​自动实现属性后面的支持字段吗?(在这个例子中,它将是_ postalCode).


编辑:我的问题不是关于设计,而是关于,比方说,理论能力.

小智 45

我不了解你,但我已经在其他公司的项目中编写代码,现在我想知道我是怎么做的!因此,通常可以更快地进行网络搜索以获得答案,并将它带到了这里.

但是,我的理由不同.我是单元测试,并不关心纯粹主义者必须说什么,但作为单元测试设置的一部分,我试图为给定对象调用某个状态.但是这个状态应该在内部控制.我不希望其他开发人员意外地搞乱状态,这可能对系统产生深远的影响.所以必须私下设置!然而,如何在不调用(希望)永远不会发生的行为的情况下对这样的事情进行单元测试?在这种情况下,我相信使用单位测试反射是有用的.

另一种方法是暴露我们不想暴露的东西,所以我们可以对它们进行单元测试!是的,我在现实生活中看到过这种情况,只是想着它仍然让我摇头.

所以,我希望下面的代码可能有用.

这里有两种方法,仅用于分离关注点,实际上,还有助于提高可读性.对于大多数开发人员而言,反思是令人头晕目眩的东西,根据我的经验,他们要么回避它,要么就像瘟疫一样避免它!

private string _getBackingFieldName(string propertyName)
{
    return string.Format("<{0}>k__BackingField", propertyName);
}

private FieldInfo _getBackingField(object obj, string propertyName)
{
    return obj.GetType().GetField(_getBackingFieldName(propertyName), BindingFlags.Instance | BindingFlags.NonPublic);
}
Run Code Online (Sandbox Code Playgroud)

我不知道你工作的代码约定,但就个人而言,我喜欢帮助方法是私有的,并以小写字母开头.我在阅读时没有发现那么明显,所以我也喜欢前面的下划线.

讨论了支持字段及其自动命名.出于单元测试的目的,如果它已经改变,你会很快知道!它对你的真实代码也不会是灾难性的,只是测试.因此,我们可以对名称的命名做出简单的假设 - 如上所述.你可能不同意,那没关系.

更难的助手_getBackingField返回其中一种反射类型,FieldInfo.我在这里也做了一个假设,你所追求的支持字段是来自一个实例的对象,而不是静态的.如果你愿意的话,你可以把它分解成可以传入的参数,但对于那些可能想要功能而不是理解的普通开发人员来说,水域肯定会更加混乱.

关于FieldInfos 的方便之处在于它们可以在与之匹配的对象上设置字段FieldInfo.用一个例子可以更好地解释这一点:

var field = _getBackingField(myObjectToChange, "State");
field.SetValue(myObjectToChange, ObjectState.Active);
Run Code Online (Sandbox Code Playgroud)

在这种情况下,该字段是一个名为的枚举类型ObjectState.名称已被更改,以保护无辜!因此,在第二行中,您可以看到通过访问FieldInfo之前返回的,我可以调用SetValue您可能认为应该已经与您的对象相关的方法,但不是!这是反射的本质 - FieldInfo将字段与其来源分开,因此您必须告诉它使用哪个实例(myObjectToChange),从而告诉它您希望它具有的值,在本例中ObjectState.Active.

因此,简而言之,面向对象编程将阻止我们执行诸如访问私有字段之类的令人讨厌的事情,更糟糕的是,当代码开发人员不打算更改它们时.哪个好!这是C#非常有价值并且受开发者喜欢的原因之一.

然而,微软给了我们反思,通过它,我们使用了强大的武器.它可能很丑,而且非常慢,但与此同时,它暴露了MSIL(MicroSoft中间语言)-IL的内部工作的最深处 - 并且使我们能够完全打破书中的每个规则,这是一个很好的例子.

  • 伙计,你是单元测试实现而不是行为,即进行白盒测试。单元测试应该可以帮助您,例如,当您想要更改实现而不是行为时。一条建议是将您的测试对象视为黑匣子。使用它的方法和不同的模拟来让它处于你想要的状态。只有我的两分钱。 (4认同)

Nic*_*pat 7

更新:https://github.com/jbevain/mono.reflection附带了一个后备字段解析器方法,该方法适用于C#,VB.NET和F#生成的自动属性.NuGet包位于https://www.nuget.org/packages/Mono.Reflection/

原文:我最终只为C#auto-properties提供了这种相当灵活的方法.正如其他答案所表明的那样,这是不可移植的,如果编译器实现使用除了以外的支持字段命名方案,则无法工作<PropertyName>k__BackingField.据我所见,C#编译器的所有实现目前都使用这种命名方案.VB.NET和F#编译器使用另一种无法使用此代码的命名方案.

private static FieldInfo GetBackingField(PropertyInfo pi) {
    if (!pi.CanRead || !pi.GetGetMethod(nonPublic:true).IsDefined(typeof(CompilerGeneratedAttribute), inherit:true))
        return null;
    var backingField = pi.DeclaringType.GetField($"<{pi.Name}>k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic);
    if (backingField == null)
        return null;
    if (!backingField.IsDefined(typeof(CompilerGeneratedAttribute), inherit:true))
        return null;
    return backingField;
}
Run Code Online (Sandbox Code Playgroud)


jam*_*wis 6

这直接来自MSDN:

在C#3.0及更高版本中,当属性访问器中不需要其他逻辑时,自动实现的属性使属性声明更简洁.它们还使客户端代码能够创建对象.当您声明属性时,如以下示例所示,编译器将创建一个私有的匿名支持字段,该字段只能通过属性的get和set访问器进行访问.

所以不,你不能.

  • 结论"所以不,你不能"是不正确的.它没有说任何关于无法访问的内容.例如,仍可以使用Reflection访问私有字段. (14认同)

Kri*_*izz 5

请参阅本文档的以下摘录:

自动实现(自动实现)的属性会自动执行此模式。更具体地说,允许非抽象属性声明具有分号访问器主体。两个访问器都必须存在并且都必须有分号主体,但它们可以有不同的可访问性修饰符。当这样指定一个属性时,将自动为该属性生成一个支持字段,并且将实现访问器以读取和写入该支持字段。支持字段的名称是编译器生成的,用户无法访问。

因此,无法访问字段。使用您的第一种方法。自动实现的属性专门用于您不需要访问支持字段的情况。


CD *_*sen 5

至少在Visual Studio 2010中,如果明确声明您需要非公共实例字段,则可以使用反射获取类中的私有字段列表:

FieldInfo[] myInfo = ClassWithPostalCode.GetType().GetFields(BindingFlags.Instance | BindingFlags.NonPublic);
Run Code Online (Sandbox Code Playgroud)

然后,您可以遍历FieldInfo数组.在这种情况下,您将看到支持字段的名称可能是

<POSTALCODE> k__BackingField

我注意到所有自动属性似乎都遵循尖括号中的属性名称模式,后跟"k__BackingField",但请记住,这不是官方的,可以在.Net的未来版本中进行更改.我不完全确定它在过去的版本中没有什么不同,就此而言.

一旦知道字段名称,就可以通过这种方式获得其值:

object oValue = obj.GetType().InvokeMember(fi.Name
    , BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance
    , null, obj, null);
Run Code Online (Sandbox Code Playgroud)