Serthy Serthy - 9 months ago 37
C# Question

Load accessor Func by reflection into Dictionary

Background: the user should be able to as efficiently reasonable as possible choose a DB-Table/Model/Class and filter/sort/display all of the public properties of it.

The names can be queried by the reflection-API, but I wondered, if these accesses can be stored and become more efficient this way?

This example shows how it could be done, however on every access it'll query the reflection-api in the func.

public class TestClass // the Model or Table
{
public int Id { get; set; }
public string Name { get; set; }
}

public static void Main( string[] args )
{
var testClasses = new TestClass[] {
new TestClass { Id = 1 , Name = "1" } ,
new TestClass { Id = 2 , Name = "2" } ,
new TestClass { Id = 3 , Name = "3" } ,
};

var propertyInfos = typeof( TestClass ).GetProperties();
var map = new Dictionary<string,Func<object,object>>(); // Func<object,object> -> Func takes an instance of the class and return a public property

// load the map once
foreach( var propertyInfo in propertyInfos )
{
Func<object,object> func = x => propertyInfo.GetValue( x );

map.Add( propertyInfo.Name , func );
}

// get the names by user-input
var names = propertyInfos.Select( x => x.Name ).ToArray();

// load the properties by name
foreach( var testClass in testClasses )
{
Console.WriteLine( $"{testClass.Id} - {testClass.Name}" );

foreach( var name in names )
{
var func = map[ name ];
var value = func( testClass ); // this is 'bad' as it uses reflection every invokation

Console.WriteLine( $"\t{name} = {value}" );
}
}
}


My question would be: can this Dictionary

var map = new Dictionary<string,Func<object,object>> {
{ "Id" , x => ( x as TestClass ).Id } ,
{ "Name" , x => ( x as TestClass ).Name } ,
};


be created automatically by just providing the Type (and without using reflection on each invokation)?

Answer Source

You could gain something by removing the reflection from each call and doing it only once:

var par = Expression.Parameter(typeof(object), "row");

// load the map once
foreach (var propertyInfo in propertyInfos)
{
    Func<object, object> func = Expression.Lambda<Func<object, object>>(Expression.Convert(Expression.Property(Expression.Convert(par, propertyInfo.DeclaringType), propertyInfo), typeof(object)), par).Compile();
    map.Add(propertyInfo.Name, func);
}

I create a vary small expression tree that casts the parameter object to the "correct" type (TestClass in this case), calls the getter of the property and the convert the result to object.