2

For the sake of simplicity take following example, which is not valid in C#:

public void SomeGenericFunc<T>(T classObject, T.Property.Type classPorpertyType)
{
    string propertyName = nameof(classPropertyType);
    // ...
}

Sample simple class:

public class Car
{
    public Car(){}

    public int DoorsCount { get; set; }

    public string Color { get; set; }
}

A function call would look something like this:

SomeGenericFunc<Car>(new Car(), Car.DoorsCount); //should be valid
SomeGenericFunc<Car>(new Car(), SomeType); //should be invalid at compile time: SomeType is not a property of Car
SomeGenericFunc<Car>(new Car(), Car.Color); //should be valid

Obviously there is nothing like T.Property.Type available in C#. In this example i don't want to pass nameof(Car.DoorsCount) everytime i call the function. I want the nameof() call to take place in SomeGenericFunc<T>(),so i can call SomeGenericFunc<T>() with any class and any of that class' property.

So is it possible to pass and expect a property type of a class in a generic function?

EDIT:

To make it clear: I DON'T want the value of the property Car.DoorsCount. I want the name of the property, without passing it directly as parameter. I want to resolve the name of the property in SomeGenericFunc<T>().

ysfaran
  • 5,189
  • 3
  • 21
  • 51
  • 1
    Do you want to get the property name or the property value? If you want the value you can define your method as `TProperty Func(T obj, Func propertyAccessor) { return propertyAccessor(obj); }` so a call to `Func(new Car(), car => car.DoorsCount)` will return the property value. If you want to get the property name/metadata you could use expressions `void Func(T obj, Expression> property) { var propertyInfo = ((MemberExpression)property.Body).Member as PropertyInfo; var propertyName = propertyInfo.Name; }` – ckuri Jun 12 '18 at 07:54
  • I'd suggest looking at reflection https://stackoverflow.com/questions/1196991/get-property-value-from-string-using-reflection-in-c-sharp – Mick Jun 12 '18 at 07:54
  • @ckuri i want the property name not the value of the property. But this should not be the point. I want to know if it is possible to say the compiler: Hey `T` is the class, i want a function parameter that only accepts a proptery of `T` i can call `nameof()` on. So in my example i would like to get `"DoorsCount"` as a string in `string propertyName`. I will have a look at your suggestions. – ysfaran Jun 12 '18 at 08:10
  • 1
    Then take a look at Sweeper's answer, which is basically the same as mine - he even removed the object parameter which I forgot - as you don't need it if you just want the property name. – ckuri Jun 12 '18 at 08:16

3 Answers3

2

Your proposal has no solution after all: you don't want to pass the name of a given property using nameof and you want to give a property to later get the whole type to get property type.

If you're looking to both receive property value and get the its name later, you may use an expression tree:

public class A 
{
    public int Value { get; set; }
}

public static S DoStuff<T, S>(T some, Expression<Func<T, S>> propertySelector)
{
   if(selector.Body is MemberExpression memberExpr)
   {
       var propertyType = memberExpr.Member;
       var propertyName = propertyType.Name;
      // Extra: as per your last edit, you don't want this, but
      // I leave the code so you're aware that you can have both
      // the property and its value
       var somePropertyValue = propertySelector.Compile()(some);

       return somePropertyValue;
   } 
   else
       throw new ArgumentException("propertySelector", "This isn't a member expression");
}

So, you would call that method as follows:

DoStuff(new A(), a => a.Value);
Matías Fidemraizer
  • 63,804
  • 18
  • 124
  • 206
2

Try using expression trees:

static void GetPropertyName<T, TProperty>(Expression<Func<T, TProperty>> property) {
    var propertyInfo = (property.Body as MemberExpression).Member;
    Console.WriteLine(propertyInfo.Name);
}

You can call it like this:

GetPropertyName((Car c) => c.DoorsCount);

Obviously, this will throw an exception if you try to pass in stuff that isn't a member expression.

You can't use nameof here because nameof is a constant expression. The expression in the brackets is not evaluated. The whole nameof expression is evaluated at compile time. This means that trying to get the name of the property passed as a parameter will just get you the name of the parameter. You need something that works at runtime, like expression trees.

Also note that you don't need an instance of Car here. What the method needs is just an expression. No property's value is actually evaluated.

Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • 1
    I believe that the `as` operator has no use here because if the cast results in a null reference, accessing `Member` will throw an exception. – Matías Fidemraizer Jun 12 '18 at 08:13
  • @MatíasFidemraizer What else can I do, other than throwing an exception, when the lambda passed in is not a member access? – Sweeper Jun 12 '18 at 08:15
  • See my edit in my own answer where I use pattern matching to match `MemberExpression`, and otherwise I throw an `ArgumentException` – Matías Fidemraizer Jun 12 '18 at 08:16
  • Also, you can use the null conditional operator: `(property.Body as MemberExpression)?.Member`. BTW, the `Console.WriteLine` may write `null` to the console :/ – Matías Fidemraizer Jun 12 '18 at 08:19
  • @MatíasFidemraizer Yeah, I suppose there are many ways to handle this case. Let's leave it to the OP to choose. – Sweeper Jun 12 '18 at 08:20
  • Anyway, I would introduce the null conditional operator in your code, because of the potential unwanted case of a `NullReferenceException` :D – Matías Fidemraizer Jun 12 '18 at 08:22
  • @sweeper can you look at my edited question and tell me if it still adresses my issue? Thanks. – ysfaran Jun 12 '18 at 08:35
  • @Truntle Yeah, my answer and Fidemraizer's answer both does that - get the property _name_ of a particular property. Did you try them? – Sweeper Jun 12 '18 at 08:36
1

Trying to stay as close as possible to given code sample while providing minimal error handling:

public static void SomeGenericFunc<T, PT>(T classObject, Expression<Func<T, PT>> expression)
{
        var targetType = typeof(T);

        var property = expression.Body as MemberExpression;
        var propertyInfo = property?.Member as PropertyInfo;

        if (property == null || propertyInfo == null)
            throw new ArgumentException("Expecting property!");

        if (propertyInfo.ReflectedType != targetType)
            throw new ArgumentException($"Property does not belong to type {targetType.Name}");

        string propertyName = propertyInfo.Name;

}

You can call it with:

SomeGenericFunc(new Car(), car => car.DoorsCount);
Cosmin Sontu
  • 1,034
  • 11
  • 16