67

I am looking for a method to pass property itself to a function. Not value of property. Function doesn't know in advance which property will be used for sorting. Simplest way in this example is: creating 4 overwrites with different parameter types. Other way is using of typeof() inside function. Both these ways are unacceptable when Class1 has hundreds properties. So far I found following method:

class Class1
{
    string vehName;
    int maxSpeed;
    int fuelCapacity;
    bool isFlying;
}

class Processor
{
    List<Class1> vehicles = null;
    Processor(List<Class1> input)
    {
        vehicles = input;
    }

    List<Class1> sortBy(List<Class1> toSort, string propName)
    {
        if (toSort != null && toSort.Count > 0)
        {
            return toSort.OrderBy(x => typeof(Class1).GetProperty(propName).GetValue(x, null)).ToList();
        }
        else return null;
    }
}

class OuterUser
{
    List<Class1> vehicles = new List<Class1>();
    // ... fill the list
    Processor pr = new Processor(vehicles);
    List<Class1> sorted = pr.sortBy("maxSpeed");
}

I don't like this method because of risk of "human error" when passing string to processing function. When the string is generated by other part of code this is going be even more ugly. Please, suggest more elegant way to implement passing of Class1 property to function for further processing. The best option for usage IMHO will be (or something like this):

vehicles = sortBy(vehicles, Class1.maxSpeed);
BenMorel
  • 34,448
  • 50
  • 182
  • 322
Ivy Growing
  • 2,688
  • 2
  • 17
  • 23
  • How is the property to be used for sorting selected in the first place? – Matt Mills Jun 24 '12 at 16:02
  • possible duplicate of [How can I pass a property of a class as a parameter of a method?](http://stackoverflow.com/questions/1178574/how-can-i-pass-a-property-of-a-class-as-a-parameter-of-a-method) – nawfal Feb 11 '13 at 10:39

7 Answers7

86

You can pass a property accessor to the method.

List<Class1> SortBy(List<Class1> toSort, Func<Class1, IComparable> getProp)
{
    if (toSort != null && toSort.Count > 0) {
        return toSort
            .OrderBy(x => getProp(x))
            .ToList();
    }
    return null;
}

You would call it like this:

var result = SortBy(toSort, x => x.maxSpeed);

But you could go one step further and write your own extension method.

public static class CollectionExtensions
{
    public static List<TSource> AsOrderedListOrNull<TSource, TKey>(
        this ICollection<TSource> collection, Func<TSource,TKey> keySelector)

        if (collection != null && collection.Count > 0) {
            return collection
                .OrderBy(x => keySelector(x))
                .ToList();
        }
        return null;
    }
}

Now you can sort like this

List<Class1> sorted = toSort.AsOrderedListOrNull(x => x.maxSpeed);

but also

Person[] people = ...;
List<Person> sortedPeople = people.AsOrderedListOrNull(p => p.LastName);

Note that I declared the first parameter as ICollection<T> because it must fulfill two conditions:

  1. It must have a Count property
  2. It must be an IEnumerable<T> in order to be able to apply the LINQ method OrderBy.

List<Class1> is an ICollection<T> but also an array Person[] as many other collections.


So far, I have shown how you can read a property. If the method needs to set a property, you need to pass it a setter delegate as well

void ReadAndWriteProperty(Func<Class1, T> getProp, Action<Class1, T> setProp)

Where T is the type of the property.

Olivier Jacot-Descombes
  • 104,806
  • 13
  • 138
  • 188
  • Does this method do anything different than LINQ's OrderBy? – Chris Gessler Jun 24 '12 at 16:09
  • @Chris Gessler: Yes, if the source collection is `null` or its `Count` is `0` then `null` is returned, otherwise a `List` is returned. LINQ's `OrderBy` throws an exception if the source is `null` and returns an `IEnumerable` otherwise. I don't know if this method is really useful to others, but the OP needs it apparently. – Olivier Jacot-Descombes Jun 24 '12 at 16:30
40

You can use an lambda expression to pass property information:

void DoSomething<T>(Expression<Func<T>> property)
{
    var propertyInfo = ((MemberExpression)property.Body).Member as PropertyInfo;
    if (propertyInfo == null)
    {
        throw new ArgumentException("The lambda expression 'property' should point to a valid Property");
    }
}

Usage:

DoSomething(() => this.MyProperty);
MatthiasG
  • 4,434
  • 3
  • 27
  • 47
  • 2
    You can use this approach, if you need to get more information about a property, like the property name for instance (useful when implementing `INotifyPropertyChanged`). However, this is kind of overkill in this situation, as it is fully sufficient to return the property value. – Olivier Jacot-Descombes Jun 24 '12 at 16:45
21

What I found missing from @MatthiasG's answer is how to get property value not just its name.

public static string Meth<T>(Expression<Func<T>> expression)
{
    var name = ((MemberExpression)expression.Body).Member.Name;
    var value = expression.Compile()();
    return string.Format("{0} - {1}", name, value);
}

use:

Meth(() => YourObject.Property);
Matas Vaitkevicius
  • 58,075
  • 31
  • 238
  • 265
8

Great solution over here...

Passing properties by reference in C#

void GetString<T>(string input, T target, Expression<Func<T, string>> outExpr)
{
    if (!string.IsNullOrEmpty(input))
    {
        var expr = (MemberExpression) outExpr.Body;
        var prop = (PropertyInfo) expr.Member;
        prop.SetValue(target, input, null);
    }
}

void Main()
{
    var person = new Person();
    GetString("test", person, x => x.Name);
    Debug.Assert(person.Name == "test");
}
Carter Medlin
  • 11,857
  • 5
  • 62
  • 68
4

Why don't you use Linq for this? Like:

vehicles.OrderBy(v => v.maxSpeed).ToList();
joakimbeng
  • 867
  • 7
  • 9
  • Other classes will use `Processor`. They shouldn't see `vehicles`. In addition `sortBy` will have more logic (ascending/descending/some-filtering)... – Ivy Growing Jun 24 '12 at 16:07
  • Then I think you should look at the answer from @olivier above, about writing your own extension method (to add filtering, etc.) – joakimbeng Jun 24 '12 at 16:13
0

Just to add from the answers above. You can also do a simple flag for the order direction.

public class Processor
{
    public List<SortableItem> SortableItems { get; set; }

    public Processor()
    {
        SortableItems = new List<SortableItem>();
        SortableItems.Add(new SortableItem { PropA = "b" });
        SortableItems.Add(new SortableItem { PropA = "a" });
        SortableItems.Add(new SortableItem { PropA = "c" });
    }

    public void SortItems(Func<SortableItem, IComparable> keySelector, bool isAscending)
    {
        if(isAscending)
            SortableItems = SortableItems.OrderBy(keySelector).ToList();
        else
            SortableItems = SortableItems.OrderByDescending(keySelector).ToList();
    }
}
cubski
  • 3,218
  • 1
  • 31
  • 33
0

I would like to give a simple, easy to understand answer.

Function's parameter is this: System.Func<class, type of the property>

And we pass the Property like this: Function(x => x.Property);

Here is the code:

class HDNData
{
    private int m_myInt;
    
    public int MyInt
    {
        get { return m_myInt; }
    }
    
    public void ChangeHDNData()
    {
        if (m_myInt == 0)
            m_myInt = 69;
        else
            m_myInt = 0;
    }
}

static class HDNTest
{
    private static HDNData m_data = new HDNData();
    
    public static void ChangeHDNData()
    {
        m_data.ChangeHDNData();
    }
    
    public static void HDNPrint(System.Func<HDNData, int> dataProperty)
    {
        Console.WriteLine(dataProperty(m_data));//Print to console the dataProperty (type int) of m_data
    }
}

//******Usage******
HDNTest.ChangeHDNData();
//This is what you want: Pass property itself (which is MyInt) to function as parameter in C#
HDNTest.HDNPrint(x => x.MyInt);
123iamking
  • 2,387
  • 4
  • 36
  • 56