在C#中将属性本身作为参数传递给函数

67

我正在寻找一种方法将属性本身传递给函数,而不是属性的值。函数事先不知道哪个属性将用于排序。在这个例子中,最简单的方法是:创建4个具有不同参数类型的重载。另一种方法是在函数内部使用 typeof()。但当 Class1 拥有数百个属性时,这些方法都无法接受。到目前为止,我找到了以下方法:

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");
}

我不喜欢这种方法,因为将字符串传递给处理函数时存在"人为错误"的风险。当字符串由代码的其他部分生成时,情况会变得更加丑陋。 请建议更优雅的方式来实现将Class1属性传递给函数进行进一步处理。在我看来,最好的选项是(或类似于此):

vehicles = sortBy(vehicles, Class1.maxSpeed);

首先,如何选择要用于排序的属性? - Matt Mills
可能是重复的问题:如何将类的属性作为方法的参数传递? - nawfal
7个回答

87

你可以将属性访问器传递给该方法。

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;
}

你可以这样调用:

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

但是你可以更进一步,编写自己的扩展方法。

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;
    }
}

现在你可以这样排序

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

但也

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

请注意,我将第一个参数声明为 ICollection<T>,因为它必须满足两个条件:
  1. 它必须具有 Count 属性
  2. 它必须是 IEnumerable<T>,以便能够应用 LINQ 方法 OrderBy
List<Class1> 是一个 ICollection<T> ,但也是像许多其他集合一样的数组 Person[]

到目前为止,我已经展示了如何读取属性。如果方法需要设置属性,则还需要传递一个setter委托。

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

其中T是属性的类型。


这个方法和LINQ的OrderBy有什么不同吗? - Chris Gessler
@Chris Gessler:是的,如果源集合为null或其Count0,则返回null,否则返回List<T>。LINQ的OrderBy如果源为null则会抛出异常,否则返回IEnumerable<T>。我不知道这个方法是否真的对其他人有用,但显然OP需要它。 - Olivier Jacot-Descombes

41

您可以使用 lambda 表达式传递属性信息:

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");
    }
}

使用方法:

DoSomething(() => this.MyProperty);

2
如果您需要获取有关属性的更多信息,例如属性名称(在实现INotifyPropertyChanged时非常有用),则可以使用此方法。但是,在这种情况下,这有点过度,因为仅返回属性值就足够了。 - Olivier Jacot-Descombes

21

我发现@MatthiasG的答案中缺少如何获取属性值而不仅仅是其名称。

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);
}

使用:

Meth(() => YourObject.Property);

8

这里有一个很棒的解决方案...

如何在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");
}

4

你为什么不使用Linq呢?例如:

vehicles.OrderBy(v => v.maxSpeed).ToList();

其他类将使用 Processor。他们不应该看到 vehicles。此外,sortBy 将具有更多的逻辑(升序/降序/某些过滤)... - Ivy Growing
那么我认为你应该看一下@olivier的答案,关于编写自己的扩展方法(添加过滤等)。 - joakimbeng

0

从上面的答案中补充一点。您还可以为订单方向设置一个简单的标志。

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();
    }
}

0
我想给出一个简单易懂的答案。
函数的参数是这样的:System.Func<class, type of the property> 我们像这样传递属性:Function(x => x.Property); 以下是代码:
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);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接