📜 ⬆️ ⬇️

Four ways to extract values ​​from hidden fields in C #

Good day. Not so long ago, an article was skipped on Habré, which showed the possibility of accessing the closed fields of an object from another instance of the same class.

public class Example { private int JustInt; // Some code here public void DoSomething(Example example) { this.JustInt = example.JustInt; //   ,   } } 


Method 1, not entirely honest: use protected fields and heirs


Suppose we have a class:

 public class SecretKeeper { private int _secret; //    //    public int Secret{get { return _secret; } set { _secret = value; }} } 

Add the protected property to it:
')
  protected int SecretForInheritors => _secret; //     _secret 

And add the class heir:

 public class SecretKeeperInheritor : SecretKeeper { public int GetSecret() { return SecretForInheritors; } } 

Check the code:

 var secret = new SecretKeeperInheritor {Secret = 42}.GetSecret(); Console.WriteLine ( secret == 42 ? "Inheritors test: passed" : "Inheritors test: failed" ); 

Sometimes the method is used for testing: adding a protected field does not change the public contract of the class, the successor is created in the test project. Helps to avoid stubs (mocks \ stubs) in test methods. A modification of this method can be considered using the internal fields and the InternalVisibleTo attribute in AssemblyInfo.

Disadvantages: you have to create / maintain an additional field, or change the old one, which requires at least access to the class. For external libraries do not apply. If the class has heirs, the class contract will change for them, which increases the likelihood of an error made in the future.

Method 2, classic: reflection with GetMemberInfo


Again we use the test class:

 public class SecretKeeper { private int _secret; //    public int Secret{get { return _secret; } set { _secret = value; }} } 

Create a static class with a method for extracting a secret:

 public static class SecretFinder { public static int GetSecretUsingFieldInfo(this SecretKeeper keeper) { FieldInfo fieldInfo = typeof (SecretKeeper).GetField("_secret", BindingFlags.Instance | BindingFlags.NonPublic); int result = (int)fieldInfo.GetValue(keeper); return result; } } 

You can test the code:

 SecretKeeper keeper = new SecretKeeper {Secret = 42}; //     int fieldInfoSecret = keeper.GetSecretUsingFieldInfo(); //   Console.WriteLine ( fieldInfoSecret == 42 ? "FieldInfo test: passed" : "FieldInfo test: failed" //    ); 

The method is suitable in cases where there is no access to the SecretKeeper code, or there is no desire to change the class contract. Sometimes this code can be seen in production: a new version of the library is being developed, access to a private field was required, the current class cannot be changed, because “it works — don't touch”. Sometimes used in testing, when changing the original class is not time. If you still use a similar option - remember about the ability to cache FieldInfo (MemberInfo).

Disadvantages: a string on the name of the field, which may come back to a halt when refactoring. In addition, reflection is a tool rather slow.

Method 3: Accelerated Classic: Reflection with ExpressionTrees


Reflection can be prepared for quick work. Consider the test class again:

 public class SecretKeeper { private int _secret; //    public int Secret{get { return _secret; } set { _secret = value; }} } 

And add to our static SecretFinder method:

 public static int GetSecretUsingExpressionTrees(this SecretKeeper keeper) { ParameterExpression keeperArg = Expression.Parameter(typeof(SecretKeeper), "keeper"); // SecretKeeper keeper argument Expression secretAccessor = Expression.Field(keeperArg, "_secret"); // keeper._secret var lambda = Expression.Lambda<Func<SecretKeeper, int>>(secretAccessor, keeperArg); var func = lambda.Compile(); //   return result = keeper._secret; return func(keeper); } 

You can test the code:

 SecretKeeper keeper = new SecretKeeper {Secret = 42}; //     int fieldInfoSecret = keeper.GetSecretUsingExpressionTrees(); //   Console.WriteLine ( fieldInfoSecret == 42 ? "ExpressionTrees test: passed" : "ExpressionTrees test: failed" //   ); 

Personally, I used this method while writing a custom serializer. The resulting functions work quietly with private fields, are cached, while the performance is two times less than the similar code written in the editor (and 8 times more than the previous example).

Disadvantages: quite complicated, even for the example above, I had to google a little. In the example above, there is also a link to the name of the property.

Method 4, for those who are not looking for easy ways


The method is based on the analogue of union structures from C.
As an example, consider the structure:

 [StructLayout(LayoutKind.Explicit, Pack = 1)] public struct StructWithSecret { [FieldOffset(0)] private int _secret; public StructWithSecret(int secret) { _secret = secret; } } 

Create a copy of it by creating a public field instead of private _secret at the same offset:

 [StructLayout(LayoutKind.Explicit, Pack = 1)] public struct Mirror { [FieldOffset(0)] public int Secret; } 

Add a structure containing both a secret and a mirror to detect it:

 [StructLayout(LayoutKind.Explicit, Pack = 1)] public struct Holmes { [FieldOffset(0)] public StructWithSecret HereIsSecret; //    [FieldOffset(0)] public Mirror LetsLookAtTheMirror; //       } 

In static SecretFinder add the method:

 public static int GetSecretFromStruct(this StructWithSecret structWithSecret) { Holmes holmes = new Holmes {HereIsSecret = structWithSecret}; //      return holmes.LetsLookAtTheMirror.Secret; //     (      )    } 

All code is tested:

 var alreadyNotSecret = new StructWithSecret(42).GetSecretFromStruct(); Console.WriteLine ( alreadyNotSecret == 42 ? "Structs test: passed" : "Structs test: failed" ); 

The scope is extremely limited: the method is available only for structures, you need to be extremely careful with offsets, the types of fields in the structures are limited, rather specific structures are required, information about alignments. And although the approach is not without a certain elegance, I can not imagine a situation in which it is justified.

In conclusion, I want to add: the first three approaches work with both getters and setters. You can also work with properties and methods. The method with heirs is not applicable for static classes (for they are sealed), the complexity of reflexive methods will increase slightly when working with Generic classes.

All good, and let your code be clear and clean.

Source: https://habr.com/ru/post/304520/


All Articles