0

I have looked at potentially duplicate questions, and this problem is not answered in any of them including Accessing Properties Through Generic Type Parameters

TLDR; typeof(T).GetProperty(propertyName, BindingFlags.NonPublic) does not work within a generic class when propertyName of T is not declared public, even when the NonPublic BindingFlag is set. Is there a workaround for this?

I have implemented an extension method that produces an IOrderedQueryable from a supplied string that contains the desired columns for sorting in an Entity Framework environment. The goal was to allow Order By clauses to be written in string form such as "+Age,+State,-Name" and produce the necessary expression to sort Entity Framework results by Age, State, and Name (descending), instead of having to write:

.OrderBy(m=>m.Age).ThenBy(m=>m.State).ThenByDescending(m=>m.Name).

The problem comes in when I try to access a class property that is marked internal. I cannot seem to find any way to force reflection to find that internal property.

In this particular case I have a datetime field in the database which is stored UTC but I want the end user to work with it in local time, so the value startTime, an internally scoped property is bound to the database and StartTime serves as a public accessor and provides access to the startTime value performing the necessary mapping to local time. I have a class defined as follows:

public class TimeInterval { 
   //other properties...
   internal startTime { get; set; } // bound to database, stores time in UTC
   public StartTime { 
      get { /*convert startTime to local time*/; } 
      set { /*convert value back to UTC and store in startTime*/;  }
   }
}

With EF I can write .OrderBy( m=>m.startTime ) and it works as expected; of course I cannot use .OrderBy( m=>m.StartTime ) because it is derived.

Within my extension function whose signature is

static public IOrderedQueryable<T> ParseOrderBy<T>( 
   this IQueryable<T> query, 
   string orderBy 
   )

which I would call using :

var query = query.ParseOrderBy<TimeInterval>( "startTime" );

my call to obtain the property reference to build a lamba expression returns null.

var property = typeof( T ).GetProperty( "startTime", BindingFlags.NonPublic );

property comes back null for anything that is not explicitly public.

The extension method is in the same namespace and assembly as the classes I expect it to operate.

Bill
  • 1,738
  • 10
  • 22
  • This might help: https://stackoverflow.com/questions/2267277/get-private-properties-method-of-base-class-with-reflection – Rui Jarimba Jun 16 '18 at 20:19
  • 2
    You don't even need reflection for this task - [`Expression.Property`](https://msdn.microsoft.com/en-us/library/bb341554(v=vs.110).aspx) will do that for you. – Ivan Stoev Jun 16 '18 at 20:49
  • @GertArnold. Sorry I forgot to mention that. I use reflection all time time, so I am aware of the binding flags. I did specify BindingFlags.NonPublic, and it still comes back NULL – Bill Jun 16 '18 at 22:47
  • @RuiJarimba No, sorry that is not the answer. I use reflection all the time and in all other cases it works. It seems that when used in a generic class as I describe, it does not allow you to access non-public members. – Bill Jun 16 '18 at 22:49
  • @IvanStoev I missed that possibility because none of the examples I saw used the actual property name as a parameter to the Expression.Property method but instead used the GetProperty method to use the PropertyInfo overload. If you want to post your comment as an answer I will mark it accepted. However I would still like to know why this aspect of generics does not allow reflection to access private members yet Expression logic does. – Bill Jun 16 '18 at 23:17
  • @IvanStoev It appears there must be a bug in Expression.Property() when supplying a string identifying the property name. I gave it startTime and it keeps finding StartTime. If I added another internal property startTime2, it finds that perfectly. – Bill Jun 17 '18 at 00:03
  • @Bill There is nothing special. `BindingFlags.NonPublic` is not enough, you must combine it with `BindingFlags.Instance`. Normally you should use `BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic`. Cheers. – Ivan Stoev Jun 17 '18 at 00:04
  • 1
    @IvanStoev This will be here for posterity: I am getting old. As mentioned previously I use reflection all the time for all sorts of things. I kept thinking that Instance was the default behavior in terms of bindings. – Bill Jun 17 '18 at 00:11

0 Answers0