Search code examples
c#reflectionanonymous-types

Getting the properties defined in an anonymous type sent via Func


Let's say we have the following classes defined like this

class Test {
    public string Name { get; set; }
    public string Type { get; set; }
    public string SomeOtherValue { get; set; }
    public Address Address { get; set; }
}

class Address {
    public string AddressText { get; set; }
    public string PostalCode { get; set; }
}

Then I have a need inside a method to do something with one or more of the properties in this class, and I would like to define which in a compile safe way. So I want to call the method like this:

MyFunction<Test>(t => new {t.Name, t.Address.AddressText});

Then inside MyFunction I need to know that "Name" and "Address.AddressText" are the selected properties, but I'm not sure how to get a hold of that

public void MyFunction<T>(Func<T, ?> param){
    ...
}

So my problem is I can't set the ? as the TResult of Func in any good way. If I could set that properly I guess I could take typeOf(TResult).GetProperties(), but I feel I'm missing a piece to get this to work.

Update

I see there is a confusion what I'm trying to do here. I'll give an example from the MongoDB driver that behaves the same way I would like mye code to do

So lets say that testcoll is a collection of Test objects. In that case this will work, and will only return me a list of an anonymous type that only has the Name property.

await testcoll.Find(t => t.Type == "sometype").Project(t => new {t.Name}).ToListAsync();

So the question is basically. Inside Project, how can I identify that Name is the properties I want to fetch from the DB, and return in the anonymous type in the result?


Solution

  • Okay, I solved this now. It might be that my explanation in the questions is the problem. I want to get compile time safety for the parameter, and not getting the actual value compile time. That would of course not work.

    So I want to be able to write

    MyFunction<Test>(t => new {t.Name, t.Address.AddressText});
    

    Instead of

    MyFunction<Test>(new {"Name", "Address.AddressText"});
    

    So that I wont get any typos in the parameters sent in, and will make sure that any change to the Test object will give me error here compile time.

    I solved it like this:

    public void MyFunction<T>(Expression<Func<T, object>> lambda)
    {
        List<string> arguments;
        if (lambda.Body is NewExpression)
        {
            var args = (lambda.Body as NewExpression).Arguments;
            arguments = args.Select(a =>
            {
                    var aText = a.ToString();
                    var dotIndex = aText.IndexOf(".");
                    var result = aText.Substring(dotIndex + 1);
                    return result;
                }).ToList();
            }
            else
            {
                throw new ArgumentException();
            }
        }
        <do whatever I need to do here with the arguments list>
    }
    

    So for the example above where the argument is t => new {t.Name, t.Address.AddressText} that will be converted to a list with the strings "Name" and "Address.AddressText" inside the method.