我知道我可以使用详细的属性语法:
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的内部工作的最深处 - 并且使我们能够完全打破书中的每个规则,这是一个很好的例子.
更新: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)
至少在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)