A bit of reflection on C# reflection

何兴邦
2023-12-01

C# reflection is supposed to fully or at least to a great extent cover all C# language constructs and features from an IL perspective. That's why I have been addicted to using it whenever possible or at least as a fall-back. However it does have some limitations and implications that we can never be too aware of.

What I've found or been told about includes,

- Performance concerns, reflection-based approach is almost always slower and requiring more runtime memory space than its non-reflection equivalent if any

- Can be incredibly harder to maintain and read; code is made significantly longer than normal

- Far more error-prone to code

- May become unusable as the code it works or depends on changes, even if it is such a slight change that most developers think shouldn't cause any problem. Considerable testing is needed when any of such changes are made.

- .NET reflection interface and implementation are subject to rapid change from version to version as well, which might affect the usability of existing code more than one has expected.

- Certain code protection or transformation approaches or processes such as obfuscation may render reflection unworkable all at once;

- Doesn't in all aspects look very elegant, symmetric from either an aesthetic point of view or a engineering one as far as the current state of reflection is concerned

- May differ from a normal approach in the context of execution, such as the context in which the assemblies are loaded.

- May be over-specific or less specific than expected so that types or other language elements retrieved are not what the user actually wanted or it is totally impossible to do it in the way she is trying and thinks would work.


The following is an example I recently came across in regards to what reflection can do but has to be done in a far less straightforward way,

The problem came with the difficulty of retrieving private members (like property, say) of an instance of a class.


In this case, I have a base class Project and a derived class ConcreteProject, where a variable called AtomicIdGenerator of long integer type is defined as a private member in the base. In one functional module of the solution where the persistence logic needs to be able to retrieve and modify any variables and entity that are required to be persisted, I did manage to get access to this private variable through reflection using BindingMode.NonPublic, which is attributed to the fact that C# reflection does enable us to do that.

abstract class Project
{
    [Persist]
    private long AtomicIdGenerator { get; set; }
    //...
}

class ConcreteProject : Project
{
    //...
}

However the problem came when I tried to go through all the persistent entities in the OO model in order to create a complete copy of the persistent part of the whole model. I found most of the data was transferred fine, except for only a few points that I identified with a data persistence (database) comparison tool I made, with one of which being related to this particular property.

After a bit of investigation and discussion with my senior, I suddenly realised that it might be due to the way reflection treats private members in a class, especially when inheritance is involved. This is a very good example of this classic problem luckily provided by the only such variable in the entire solution we have built up so far.

The detailed explanation is reflection doesn't look at the accessibility of a class member the same way as we coders are used to, especially for those non-public ones such as protected, private and internal. Firstly they treat them on an IL level and from an IL perspective, which means they break down the concept of for instance 'private' into its real meaning which is being not accessible from outside of the class and specific (private) to this particular class only, which is pretty much a combination of 'protected' and 'confined in the class' with an 'and' logic applied to them. That's why one can't access it through reflection on the derived class which is what I get on the first occasion from an entity, which in this case an instance of B. To make it simple, a private member of a base class is, unlike a protected member of the base class, not a 'non-public' member of that class. So in order to do what I want I have to traverse the inheritance hierarchy of the instance to find out all the private members I need to deal with, and some ugly tricks need to be done that instead of relying on non-existent 'private' binding mode I may have to have a hash set or something to keep track of all the members I have dealt with and look for only those I haven't from a collection of members returned by the reflection call with an inevitably less restricted selection criterion say BindingMode.NonPublic among others.

What I wanted to mention in the end of this article is that the reason why I didn't get into this problem previously was simply because in preparing mapping for persistence I work with each specific type as it is, so there's no need to look into base classes in the hierarchy.







 类似资料: