Подтвердить что ты не робот

`Свойство собственности Type.GetProperties`

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

В документации 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 поднимает цепочку наследования снизу вверх при обнаружении свойств. Это прекрасно и может быть обработано как таковое.

Вопросы:

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

Одним из кажущихся очевидным решений было бы определить пользовательский атрибут, который указывает порядок, в котором должны появляться свойства (аналогично свойству 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 свойств, и вам нужно добавить один из двух первых.

UPDATE

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

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

4b9b3361

Ответ 1

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

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

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

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

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

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

Файл 1

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

Файл 2

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

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


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

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

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

Ответ 2

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

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

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

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

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

Ответ 3

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

Во-первых, пользовательский атрибут:

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 в сортируемый int:

    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;

Ответ 5

1

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

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

2

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

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