Сортировать список <T>, используя выражения запроса - PullRequest
13 голосов
/ 30 марта 2009

У меня проблема с использованием Linq для заказа такой структуры:

public class Person
{
    public int ID { get; set; }
    public List<PersonAttribute> Attributes { get; set; }
}

public class PersonAttribute
{
    public int ID { get; set; }
    public string Name { get; set; }
    public string Value { get; set; }
}

Человек может пойти так:

PersonAttribute Age = new PersonAttribute { ID = 8, Name = "Age", Value = "32" };
PersonAttribute FirstName = new PersonAttribute { ID = 9, Name = "FirstName", Value = "Rebecca" };
PersonAttribute LastName = new PersonAttribute { ID = 10, Name = "LastName", Value = "Johnson" };
PersonAttribute Gender = new PersonAttribute { ID = 11, Name = "Gender", Value = "Female" };

Я хотел бы использовать проекцию LINQ для сортировки списка лиц по возрастанию по атрибуту персонажа по моему выбору, например, сортировки по возрасту или сортировки по имени.

Я пытаюсь что-то вроде

string mySortAttribute = "Age"
PersonList.OrderBy(p => p.PersonAttribute.Find(s => s.Name == mySortAttribute).Value);

Но синтаксис меня подводит. Есть какие-нибудь подсказки?

Ответы [ 7 ]

9 голосов
/ 30 марта 2009

OrderBy - это расширение LINQ, которое создает новую последовательность. Чтобы упорядочить существующую последовательность, вам нужно добавить метод расширения или два ... тогда вы можете использовать:

PersonList.Sort(p => p.Attributes.Find(
  s => s.Name == mySortAttribute).Value);

public static class ListExtensions {
  public static void Sort<TSource, TValue>(
    this List<TSource> source,
    Func<TSource, TValue> selector)
  {
    var comparer = Comparer<TValue>.Default;
    source.Sort((x, y) => comparer.Compare(selector(x), selector(y)));
  }
  public  static void SortDescending<TSource, TValue>(
    this List<TSource> source,
    Func<TSource, TValue> selector)
  {
    var comparer = Comparer<TValue>.Default;
    source.Sort((x, y) => comparer.Compare(selector(y), selector(x)));
  }
}
8 голосов
/ 22 апреля 2010

Я знаю, что это старый пост, но я думал, что выложу сравнитель, который я нашел некоторое время назад, на случай, если кому-то еще понадобится.

public class GenericComparer<T> : IComparer<T>
 {
     public string SortExpression { get; set; }
     public int SortDirection { get; set; } // 0:Ascending, 1:Descending

     public GenericComparer(string sortExpression, int sortDirection)
     {
         this.SortExpression = sortExpression;
         this.SortDirection = sortDirection;
     }
     public GenericComparer() { }

     #region IComparer<T> Members
     public int Compare(T x, T y)
     {
         PropertyInfo propertyInfo = typeof(T).GetProperty(SortExpression);
         IComparable obj1 = (IComparable)propertyInfo.GetValue(x, null);
         IComparable obj2 = (IComparable)propertyInfo.GetValue(y, null);

         if (SortDirection == 0)
         {
             return obj1.CompareTo(obj2);
         }
         else return obj2.CompareTo(obj1);
     }
     #endregion
 }

Использование

List<MyObject> objectList = GetObjects(); /* from your repository or whatever */
objectList.Sort(new GenericComparer<MyObject>("ObjectPropertyName", (int)SortDirection.Descending));
dropdown.DataSource = objectList;
dropdown.DataBind();

Вы можете перегрузить конструктор, чтобы принять перечисление SortDirection. Я этого не делал, потому что класс находится в библиотеке без ссылки на System.Web.

5 голосов
/ 30 марта 2009

Почему бы вам не использовать словарь значений ключей вместо списка ? Думаю, это подойдет лучше и облегчит все остальное.

Обновление - вот так:

public class Person
{
  public Dictionary<string, string> Attributes = new Dictionary<string,string>();
}

List<Person> people = new List<Person>();

Person rebecca = new Person();
rebecca.Attributes["Age"] = "32";
rebecca.Attributes["FirstName"] = "Rebecca";
rebecca.Attributes["LastName"] = "Johnson";
rebecca.Attributes["Gender"] = "Female";
people.Add(rebecca);

var PeopleInAgeOrder = people.OrderBy(p => p.Attributes["Age"]);
1 голос
/ 30 марта 2009

Предполагается, что класс Attribute реализует IComparable или имеет хорошую функцию ToString (я надеюсь).

var list = personList.OrderBy(p => p.Attributes.FirstOrDefault(a => a.Name == "Age"))

В противном случае синтаксис становится более запутанным:

var list = personList
            .OrderBy(p => 
                     p.Attributes.FirstOrDefault(a => a.Name == "Age") == null ?
                     "" : p.Attributes.First(a => a.Name == "Age").Value
            );

Я также предполагаю, что у вас есть одно значение для каждого ключа - в противном случае вам понадобится более умный код ...; -)

0 голосов
/ 23 апреля 2010

Некоторые случаи, которые вам нужно рассмотреть:

  • Поскольку ваш атрибут в строке, возраст "30" и "3" будут заказаны до возраста "4"
  • Атрибут может не существовать

Если вы создаете этот класс методов расширения:

public static class ListExtenstions
{
    public static List<Person> OrderList(this List<Person> list, string attributeName, PersonAttribute defaultAttribute)
    {
        return OrderList(list, attributeName, defaultAttribute, x => x);
    }

    public static List<Person> OrderList<T>(this List<Person> list, string attributeName, PersonAttribute defaultAttribute, Func<string, T> convertion)
    {
        return list.OrderBy(x => convertion((x.Attributes.FirstOrDefault(y => y.Name == attributeName) ?? defaultAttribute).Value)).ToList();

        // Query Syntax
        //return
        //    (from p in list
        //     let attribute = p.Attributes.FirstOrDefault(a => a.Name == attributeName) ?? defaultAttribute
        //     orderby attribute.Value
        //     select p).ToList();
    }
}

Затем вы можете правильно отсортировать список следующим образом:

List<Person> persons = ...
...
PersonAttribute defaultAttribute = new PersonAttribute() { Value = "0" };
var ordered = persons.OrderList("Age", defaultAttribute, x => Convert.ToInt32(x));

Это даст правильный порядок сортировки. Если атрибут всегда будет присутствовать, вы можете удалить defaultAttribute.

Для сортировки по «Имени» просто используйте:

List<Person> persons = ...
...
PersonAttribute defaultAttribute = new PersonAttribute() { Value = String.Empty };
var ordered persons.OrderList("Name", defaultAttribute);
0 голосов
/ 30 марта 2009

Я полагаю, что вы получаете исключение, когда один элемент не имеет атрибута возраста. Я попробовал приведенный ниже код, и он работал нормально - я предполагаю, что ваши данные немного ошибочны, как указано другими авторами. Во всяком случае, ниже работает нормально ...

    List<Person> personList = new List<Person>();
    Random rand = new Random();

    //generate 50 random persons
    for (int i = 0; i < 50; i++)
    {
        Person p = new Person();
        p.Attributes = new List<PersonAttribute>();
        p.Attributes.Add(new PersonAttribute() { ID = 8, Name = "Age", Value = rand.Next(0, 100).ToString() });
        p.Attributes.Add(new PersonAttribute() { ID = 10, Name = "Name", Value = rand.Next(0, 100).ToString() });
        personList.Add(p);
    }

    var finalList = personList.OrderBy(c => c.Attributes.Find(a => a.Name == "Age").Value).ToList();
0 голосов
/ 30 марта 2009

Может быть, ваш синтаксис неправильный? Ваше свойство называется Атрибуты, но вы используете что-то под названием ObjectSettings в коде? Или это опечатка.

Если это так, то ваш код выглядит нормально, если не у всех экземпляров Person есть Атрибут, который вы пытаетесь упорядочить, в этом случае вы получите исключение.

EDIT: Также, вместо использования Find, попробуйте использовать First.

PersonList.OrderBy(p => p.Attributes.First(a => a.Name == "Age").Value)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...