Типы перечислений в контрактах данных

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

Основные сведения о перечислениях

Одним из способов использования типов перечисления в модели контрактов данных является применение к типу атрибута DataContractAttribute. Затем необходимо применить атрибут EnumMemberAttribute к каждому члену, включаемому в контракт данных.

В следующем примере показаны два класса. Первый использует перечисление, а второй - определяет его.

[DataContract]
public class Car
{
    [DataMember]
    public string model;
    [DataMember]
    public CarConditionEnum condition;
}

[DataContract(Name = "CarCondition")]
public enum CarConditionEnum
{
    [EnumMember]
    New,
    [EnumMember]
    Used,
    [EnumMember]
    Rental,
    Broken,
    Stolen
}
<DataContract()> _
Public Class Car
    <DataMember()> _
    Public model As String
    <DataMember()> _
    Public condition As CarConditionEnum
End Class

<DataContract(Name:="CarCondition")> _
Public Enum CarConditionEnum
    <EnumMember> NewCar
    <EnumMember> Used
    <EnumMember> Rental
    Broken
    Stolen
End Enum

Экземпляр класса Car можно отправить или получить, только если полю condition задано значение New, Used или Rental. Если condition имеет значение Broken или Stolen, создается исключение SerializationException.

Свойства DataContractAttribute (Name и Namespace) можно использовать в контрактах данных перечисления как обычно.

Значения членов перечисления

Как правило, контракт данных содержит имена членов перечисления, а не числовые значения. Однако при использовании модели контракта данных, если получатель является клиентом WCF, экспортируемая схема сохраняет числовые значения. Обратите внимание, что это не так, когда используется класс XmlSerializer.

В предыдущем примере, если condition задано значение Used и данные сериализуются в формат XML, получается XML-код <condition>Used</condition>, а не <condition>1</condition>. Поэтому следующий контракт данных эквивалентен контракту данных CarConditionEnum.

[DataContract(Name = "CarCondition")]
public enum CarConditionWithNumbers
{
    [EnumMember]
    New = 10,
    [EnumMember]
    Used = 20,
    [EnumMember]
    Rental = 30,
}
<DataContract(Name:="CarCondition")> _
Public Enum CarConditionWithNumbers
    <EnumMember> NewCar = 10
    <EnumMember> Used = 20
    <EnumMember> Rental = 30
End Enum

Например, на отправляющей стороне можно использовать CarConditionEnum, а CarConditionWithNumbers - на получающей. Хотя отправляющая сторона использует для Used значение "1", а получающая сторона - "20", представлением XML для обеих сторон является <condition>Used</condition>.

Для включения в контракт данных необходимо применить атрибут EnumMemberAttribute. В платформа .NET Framework всегда можно применить специальное значение 0 (ноль) к перечислению, которое также является значением по умолчанию для любого перечисления. Однако даже это специальное нулевое значение не удастся сериализовать, если оно не помечено атрибутом EnumMemberAttribute.

Существует два исключения из этого правила.

  • Во-первых, перечисления флагов (см. далее в этом разделе).

  • Во-вторых, члены данных перечисления, свойству EmitDefaultValue которых задано значение false (в этом случае перечисление с нулевым значением исключается из сериализованных данных).

Настройка значений членов перечисления

Настройка значения членов перечисления, входящего в состав контракта данных, осуществляется с помощью свойства Value атрибута EnumMemberAttribute.

Так, следующий контракт данных также эквивалентен контракту данных перечисления CarConditionEnum.

[DataContract(Name = "CarCondition")]
public enum CarConditionWithDifferentNames
{
    [EnumMember(Value = "New")]
    BrandNew,
    [EnumMember(Value = "Used")]
    PreviouslyOwned,
    [EnumMember]
    Rental
}
<DataContract(Name:="CarCondition")> _
Public Enum CarConditionWithDifferentNames
    <EnumMember(Value:="New")> BrandNew
    <EnumMember(Value:="Used")> PreviouslyOwned
    <EnumMember> Rental
End Enum

При сериализации значение PreviouslyOwned имеет представление XML <condition>Used</condition>.

Простые перечисления

Кроме того, можно сериализовать типы перечислений, к которым не был применен атрибут DataContractAttribute. Эти типы перечислений обрабатываются точно так же, как описано выше, за исключением того, что каждый член (к которому не применяется атрибут NonSerializedAttribute) обрабатывается, как если бы атрибут EnumMemberAttribute применялся. Например, следующее перечисление неявно содержит контракт данных, эквивалентный контракту, описанному в примере CarConditionEnum.

public enum CarCondition
{
    New,
    Used,
    Rental,
    [NonSerialized]
    Lost
}
Public Enum CarCondition
    [New]
    Used
    Rental
End Enum

Если не нужно настраивать имя и пространство имен контракта данных перечисления, а также значения членов перечисления, можно использовать простые перечисления.

Замечания о простых перечислениях

Применение атрибута EnumMemberAttribute к простым перечислениям не дает результата.

Не имеет значения, применяется ли атрибут SerializableAttribute к перечислению.

Тот факт, что класс DataContractSerializer учитывает атрибут NonSerializedAttribute, применимый к членам перечисления, отличает его от поведения классов BinaryFormatter и SoapFormatter. Оба сериализатора игнорируют атрибут NonSerializedAttribute.

Перечисления флагов

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

Для этого нужно применить атрибут DataContractAttribute к перечислению флагов, а затем пометить атрибутом EnumMemberAttribute все члены, являющиеся степенями двух. Обратите внимание, что использование перечисления флагов возможно только в том случае, если имеется непрерывная последовательность степеней двух (например, 1, 2, 4, 8, 16, 32, 64).

Чтобы отправить значение перечисления флага, нужно выполнить следующие действия.

  1. Попытайтесь найти член перечисления (с примененным атрибутом EnumMemberAttribute), который сопоставляется числовому значению. Если удается найти этот член, отправьте список, содержащий только этот член.

  2. Попытайтесь представить числовое значение в виде суммы так, чтобы имелись члены перечисления (каждый с примененным атрибутом EnumMemberAttribute), сопоставляемые каждой части этой суммы. Отправьте список всех этих членов. Обратите внимание, что жадный алгоритм используется для поиска такой суммы, и поэтому нет никакой гарантии, что такая сумма найдена даже в том случае, если она присутствует. Чтобы избежать этой проблемы, нужно убедиться, что числовые значения членов перечисления представляют собой степени двух.

  3. Если не удается выполнить предыдущие два шага и числовое значение ненулевое, создайте исключение SerializationException. Если числовое значение равно нулю, отправьте пустой список.

Пример

Следующий пример перечисления можно использовать в операции флага.

[DataContract][Flags]
public enum CarFeatures
{
    None = 0,
    [EnumMember]
    AirConditioner = 1,
    [EnumMember]
    AutomaticTransmission = 2,
    [EnumMember]
    PowerDoors = 4,
    AlloyWheels = 8,
    DeluxePackage = AirConditioner | AutomaticTransmission | PowerDoors | AlloyWheels,
    [EnumMember]
    CDPlayer = 16,
    [EnumMember]
    TapePlayer = 32,
    MusicPackage = CDPlayer | TapePlayer,
    [EnumMember]
    Everything = DeluxePackage | MusicPackage
}
<DataContract(), Flags()> _
Public Enum CarFeatures
    None = 0
    <EnumMember> AirConditioner = 1
    <EnumMember> AutomaticTransmission = 2
    <EnumMember> PowerDoors = 4
    AlloyWheels = 8
    DeluxePackage = AirConditioner Or AutomaticTransmission Or PowerDoors Or AlloyWheels
    <EnumMember> CDPlayer = 16
    <EnumMember> TapePlayer = 32
    MusicPackage = CDPlayer Or TapePlayer
    <EnumMember> Everything = DeluxePackage Or MusicPackage
End Enum

Следующие значения из примера сериализуются, как указано.

CarFeatures cf1 = CarFeatures.AutomaticTransmission;
//Serialized as <cf1>AutomaticTransmission</cf1>

CarFeatures cf2 = (CarFeatures)5;
//Serialized as <cf2>AirConditioner PowerDoors</cf2> since 5=1+4

CarFeatures cf3 = CarFeatures.MusicPackage;
//Serialized as <cf3>CDPlayer TapePlayer</cf3> since MusicPackage itself is not an EnumMember

CarFeatures cf4 = CarFeatures.Everything;
//Serialized as <cf4>Everything</cf4> since Everything itself is an EnumMember

CarFeatures cf5 = CarFeatures.DeluxePackage;
//Throws a SerializationException since neither DeluxePackage nor AlloyWheels are EnumMembers

CarFeatures cf6 = CarFeatures.None;
//Serialized as the empty list <cf6></cf6> since there is no EnumMember mapped to zero
Private cf1 As CarFeatures = CarFeatures.AutomaticTransmission
'Serialized as <cf1>AutomaticTransmission</cf1>

Private cf2 As CarFeatures = ctype(5, CarFeatures)
'Serialized as <cf2>AirConditioner PowerDoors</cf2> since 5=1+4

Private cf3 As CarFeatures = CarFeatures.MusicPackage
'Serialized as <cf3>CDPlayer TapePlayer</cf3> since MusicPackage 
' itself is not an EnumMember.

Private cf4 As CarFeatures = CarFeatures.Everything
'Serialized as <cf4>Everything</cf4> since Everything itself is an EnumMember.

Private cf5 As CarFeatures = CarFeatures.DeluxePackage
'Throws a SerializationException since neither DeluxePackage nor 
' AlloyWheels are EnumMembers.

Private cf6 As CarFeatures = CarFeatures.None
'Serialized as the empty list <cf6></cf6> since there is no EnumMember mapped to zero.

См. также