Ah, finally I got it. Maybe an interesting problem, but not making much sense.—SA
Here is the thing: you are facing some limitation which is not 100% logically required. Your code sample looks convincing (not many people will really understand your explanation of it, it just so happens that I am busy with related things right now :-)). Who knows why such limitation was applied? Maybe only people who made this decision.
Nevertheless, this limitation makes perfect practical sense. I would speculate in this way: those Microsoft people just decided: "we don't want to deal with pathological cases which hardly can make a lot of practical sense anyway". Do you understand what pathological sense I mean? Look at your
ClassB. This is a generic type where the generic parameter is not used. And this case is pathological in some sense, which I will try to explain later.
First, imagine that you omitted the generic parameter. It is quite obvious that your
GetValue works in this case. It the generic parameter is never used, you could remove it painlessly. Theoretically, I could imagine that the system would treat such "pathological" type as equivalent to the terminal (instantiated) type without generic parameters. However, it would make the system more complex and harder to understand. All irregularities "for special cases" are usually bad. Throwing the exception you observe is always better and more understandable.
Now, from the other hand, imagine that the generic parameter is actually used. If it was used in the fields from your
fieldInfos, it is absolutely apparent that your code would not make any sense, whatsoever. Really, the return result of
GetValue has the compile-time type
System.Object, but it should have some run-time type, which is supposed to be of the type
T which… does not exist, because your metadata is obtained from the incomplete (generic) type
I don't even think that using
GetValue could be theoretically possible even if all the fields of
fieldInfos were non-generic. (It is possible because you only take public members, according to your
BindingFlags, so only some non-public members could be generic.) It's harder to see, but my idea is this: the key fact is that
ClassB is not the type, so it does not have certain memory layout. The size of the generic parameter is unknown, so it could be infeasible to implement
GetValue, because the memory offsets of even non-generic type are unknown. It needs getting into further detail, but at least it looks like a real obstacle to me.
The key problem here is that you are using a metadata from a generic type with the instance of a real (fully instantiated, non-generic) type. Those two type are unrelated:
ClassA is assignment compatible with
ClassB<string> but not with
ClassB<>, which cannot have instances at all. A generic type is not a type at all. (The term "generic type" suggest it is, as it suggests a statement "a generic type is a type", which is not true; no wonder in C++ they are called "templates" and in Ada "generics"; the expression "generic type" is not the best, somewhat confusing.)
I think the problem should be quite clear by now.
As to the solution… strictly speaking, there is no solution, because there is no a problem. I see nothing practically blocking in your case. The "pathological" case you show makes no practical sense, and non-trivial case will not allow getting field by reflection not because something prevents you to do that, but because it makes no sense at all.
The practical conclusion is: with
GetValue, you always get some instance. And the instance is only possible if no partially-instantiated generic types are involved. Before getting any instance, or getting metadata required for instantiation, you need to fully instantiate the type(s) involved. This instantiation does not contradict your requirement to avoid instantiation of the instances of type (for the purpose of getting some "sample instance"), because generic type instantiation has nothing to do with type instantiation: generic type instantiation generates a type during compilation, but type instantiation generates and instance during run time. (Please see my comment to the question.)
I'm sure this Microsoft limitation does not impose any practical limitation to any non-nonsense architecture. If you don't think this is true, I would gladly discuss your architecture if you chose to share it with us, and if it does not take too much time.