Порядок свойств `Type.GetProperties` - PullRequest
22 голосов
/ 15 января 2012

Короткая версия

В документации MSDN для Type.GetProperties указано, что возвращаемая коллекция не гарантируется в алфавитном порядке или в порядке декларирования, хотя и выполняетсяпростой тест показывает, что в общем случае он возвращается в порядке декларирования.Есть ли конкретные сценарии, о которых вы знаете, где это не так?Кроме того, что является предлагаемой альтернативой?

Подробная версия

Я понимаю документацию MSDN для Type.GetProperties состояний:

Метод GetProperties не возвращает свойства в определенном порядке, например в алфавитном порядке или в порядке объявления.Ваш код не должен зависеть от порядка, в котором возвращаются свойства, поскольку этот порядок варьируется.

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

Пример:

class Simple
{
    public int FieldB { get; set; }
    public string FieldA { get; set; }
    public byte FieldC { get; set; }
}
class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Simple Properties:");
        foreach (var propInfo in typeof(Simple).GetProperties())
            Console.WriteLine("\t{0}", propInfo.Name);
    }
}

Вывод:

Simple Properties:
        FieldB
        FieldA
        FieldC

Одним из таких случаев, когда это отличается незначительно, является случай, когда у рассматриваемого типа есть родительский объект, который также обладает свойствами:

class Parent
{
    public int ParentFieldB { get; set; }
    public string ParentFieldA { get; set; }
    public byte ParentFieldC { get; set; }
}

class Child : Parent
{
    public int ChildFieldB { get; set; }
    public string ChildFieldA { get; set; }
    public byte ChildFieldC { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Parent Properties:");
        foreach (var propInfo in typeof(Parent).GetProperties())
            Console.WriteLine("\t{0}", propInfo.Name);

        Console.WriteLine("Child Properties:");
        foreach (var propInfo in typeof(Child).GetProperties())
            Console.WriteLine("\t{0}", propInfo.Name);

    }
}

Выходные данные:

Parent Properties:
        ParentFieldB
        ParentFieldA
        ParentFieldC
Child Properties:
        ChildFieldB
        ChildFieldA
        ChildFieldC
        ParentFieldB
        ParentFieldA
        ParentFieldC

Что означаетметод GetProperties перемещается вверх по цепочке наследования снизу вверх при обнаружении свойств.Это нормально и может быть обработано как таковое.

Вопросы:

  1. Существуют ли конкретные ситуации, в которых описанное поведение будет отличаться, что я пропустил?
  2. Если зависитдля заказа не рекомендуется, тогда что является рекомендуемым подходом?

Одним, казалось бы, очевидным решением будет определение пользовательского атрибута, который указывает порядок, в котором свойства должно появиться (аналогично Order свойству атрибута DataMember).Что-то вроде:

public class PropOrderAttribute : Attribute
{
    public int SeqNbr { get; set; }
}

И затем реализовать такие как:

class Simple
{
    [PropOrder(SeqNbr = 0)]
    public int FieldB { get; set; }
    [PropOrder(SeqNbr = 1)]
    public string FieldA { get; set; }
    [PropOrder(SeqNbr = 2)]
    public byte FieldC { get; set; }
}

Но, как многие обнаружили, это становится серьезной проблемой обслуживания, если ваш тип имеет 100 свойств, и вам нужно добавитьодин между первыми 2.

ОБНОВЛЕНИЕ

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

В настоящее время он работает, просто перебирая возвращаемую коллекцию из GetProperties, но поскольку в документации говоритсяне рекомендуется, я искал, чтобы понять, почему и какой другой вариант у меня есть?

Ответы [ 5 ]

14 голосов
/ 15 января 2012

Заказ просто не гарантирован; что бы ни случилось .... Бывает.

Очевидные случаи, когда это может измениться:

  • все, что реализует ICustomTypeDescriptor
  • что-нибудь с TypeDescriptionProvider

Но более тонкий случай: частичные классы. Если класс разделен на несколько файлов, порядок их использования не определяется вообще. См. Является ли "текстовый порядок" между частичными классами формально определенным?

Конечно, он не определен даже для одного (не частичного) определения; p

Но представьте себе

Файл 1

partial class Foo {
     public int A {get;set;}
}

Файл 2

partial class Foo {
    public int B {get;set:}
}

Здесь нет формального порядка декларирования между A и B. См. Связанный пост, чтобы увидеть, как, однако, имеет тенденцию к .


Re ваше редактирование; лучший подход - указать информацию о маршале отдельно; общий подход состоит в том, чтобы использовать пользовательский атрибут, который принимает числовой порядок, и украшать элементы этим. Затем вы можете заказать на основе этого номера. Protobuf-Net делает нечто очень похожее, и, честно говоря, я бы предложил использовать здесь существующую библиотеку сериализации:

[ProtoMember(n)]
public int Foo {get;set;}

Где "n" - это целое число. В частности, в случае с protobuf-net имеется также API для указания этих номеров отдельно, что полезно, когда тип не находится под вашим непосредственным контролем.

4 голосов
/ 06 июля 2015

Как бы то ни было, сортировка по MetadataToken показалась мне подходящей.

GetType().GetProperties().OrderBy(x => x.MetadataToken)

Оригинальная статья: http://www.sebastienmahe.com/v3/seb.blog/2010/03/08/c-reflection-getproperties-kept-in-declaration-order/

4 голосов
/ 28 мая 2014

Я использую пользовательские атрибуты для добавления необходимых метаданных самостоятельно (он используется с сервисом, похожим на REST, который потребляет и возвращает пары Key = Value с разделителями CRLF.

Сначала настраиваемый атрибут:

class ParameterOrderAttribute : Attribute
{
    public int Order { get; private set; }
    public ParameterOrderAttribute(int order)
    {
        Order = order;
    }
}

Затем украсьте свои классы:

class Response : Message
{
    [ParameterOrder(0)]
    public int Code { get; set; }
}

class RegionsResponse : Response 
{
    [ParameterOrder(1)]
    public string Regions { get; set; }
}

class HousesResponse : Response
{
    public string Houses { get; set; }
}

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

    private int PropertyOrder(PropertyInfo propInfo)
    {
        int output;
        var orderAttr = (ParameterOrderAttribute)propInfo.GetCustomAttributes(typeof(ParameterOrderAttribute), true).SingleOrDefault();
        output = orderAttr != null ? orderAttr.Order : Int32.MaxValue;
        return output;
    }

Еще лучше, напишите как расширение:

static class PropertyInfoExtensions
{
    private static int PropertyOrder(this PropertyInfo propInfo)
    {
        int output;
        var orderAttr = (ParameterOrderAttribute)propInfo.GetCustomAttributes(typeof(ParameterOrderAttribute), true).SingleOrDefault();
        output = orderAttr != null ? orderAttr.Order : Int32.MaxValue;
        return output;
    }
}

Наконец, теперь вы можете запросить объект Type с помощью:

        var props = from p in type.GetProperties()
                    where p.CanWrite
                    orderby p.PropertyOrder() ascending
                    select p;
4 голосов
/ 19 января 2012

1:

Я потратил последний день на устранение проблемы в проекте MVC 3, и все сводилось к этой конкретной проблеме.По сути, это основывалось на том, что порядок свойств был одинаковым на протяжении всей сессии, но в некоторых случаях некоторые свойства менялись местами, портя сайт.

Сначала код, называемый Type.GetProperties(), определяет имена столбцов в динамической таблице jqGrid, что в этом случае происходит один раз за page_load.В последующие времена вызывался метод Type.GetProperties(), который заполнял фактические данные для таблицы, а в некоторых редких случаях свойства менялись местами и полностью портили представление.В некоторых случаях переключались другие свойства, на которые опирался сайт для иерархической подсетки, т. Е. Вы больше не могли видеть вложенные данные, поскольку столбец идентификатора содержал ошибочные данные.Другими словами: да, это определенно может произойти .Осторожно.

2:

Если вам нужен согласованный порядок в течение сеанса системы, но не обязательно одинаковый порядок для всех сеансов, обходной путь - очень простой : сохранить массив PropertyInfo[], полученный из Type.GetProperties(), в качестве значения в веб-кэше или в словаре с типом (или типом имени) в качестве ключа кэша / словаря.Впоследствии, когда вы собираетесь сделать Type.GetProperties(), вместо этого замените его на HttpRuntime.Cache.Get(Type/Typename) или Dictionary.TryGetValue(Type/Typename, out PropertyInfo[]).Таким образом, вы всегда будете получать заказ, с которым столкнулись в первый раз.

Если вам всегда нужен один и тот же порядок (т. Е. Для всех системных сессий), я предлагаю вам объединить описанный выше подход с некоторым типоммеханизм конфигурации, т.е. укажите порядок в файле web.config / app.config, отсортируйте массив PropertyInfo[], полученный из Type.GetProperties(), в соответствии с указанным порядком, а затем сохраните его в кэше / статическом словаре.

4 голосов
/ 15 января 2012

Опираясь на детали реализации, которые явно задокументированы как не гарантировано - это путь к катастрофе.

«Рекомендуемый подход» будет зависеть от того, что вы хотите сделать с этимисвойства, как только у вас есть.Просто отображать их на экране?Документы MSDN группируются по типу элемента (свойство, поле, функция), а затем располагаются в алфавитном порядке внутри групп.

Если формат вашего сообщения зависит от порядка полей, то вам потребуется:

  1. Укажите ожидаемый порядок в определенном определении сообщения.Буферы протокола Google работают, если я помню, определение сообщения в этом случае компилируется из файла .proto в файл кода для использования на любом языке, с которым вы работаете.

  2. Положитесь на порядок, который может быть сгенерирован независимо, например, в алфавитном порядке.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...