Dela via


Samlingstyper i datakontrakt

En samling är en lista över objekt av en viss typ. I .NET Framework kan sådana listor representeras med hjälp av matriser eller en mängd andra typer (Allmän lista, Allmän BindingList<T>, StringCollectioneller ArrayList). En samling kan till exempel innehålla en lista med adresser för en viss kund. Dessa samlingar kallas listsamlingar, oavsett deras faktiska typ.

Det finns en särskild samlingsform som representerar en association mellan ett objekt ("nyckeln") och ett annat ("värdet"). I .NET Framework representeras dessa av typer som Hashtable och den allmänna ordlistan. En associationssamling kan till exempel mappa en stad ("nyckel") till dess befolkning ("värde"). Dessa samlingar kallas ordlistesamlingar, oavsett deras faktiska typ.

Samlingar får särskild behandling i datakontraktsmodellen.

Typer som implementerar IEnumerable gränssnittet, inklusive matriser och generiska samlingar, identifieras som samlingar. Av dessa är typer som implementerar IDictionary eller Allmänna IDictionary<TKey,TValue> gränssnitt ordlistesamlingar. Alla andra är listsamlingar.

Ytterligare krav på samlingstyper, till exempel att ha en metod som heter Add och en parameterlös konstruktor, beskrivs i detalj i följande avsnitt. Detta säkerställer att samlingstyper kan både serialiseras och deserialiseras. Det innebär att vissa samlingar inte stöds direkt, till exempel Generic ReadOnlyCollection<T> (eftersom den inte har någon parameterlös konstruktor). Information om hur du kringgår dessa begränsningar finns dock i avsnittet "Använda samlingsgränssnittstyper och skrivskyddade samlingar" senare i det här avsnittet.

Typerna som ingår i samlingar måste vara datakontraktstyper, eller vara i övrigt serialiserbara. Mer information finns i Typer som stöds av Data Contract Serializer.

Mer information om vad som är och vad som inte anses vara en giltig samling samt hur samlingar serialiseras finns i informationen om serialisering av samlingar i avsnittet "Avancerade insamlingsregler" i det här avsnittet.

Utbytbara samlingar

Alla listsamlingar av samma typ anses ha samma datakontrakt (såvida de inte anpassas med hjälp av CollectionDataContractAttribute attributet, vilket beskrivs senare i det här avsnittet). Följande datakontrakt är alltså likvärdiga.

[DataContract(Name = "PurchaseOrder")]
public class PurchaseOrder1
{
    [DataMember]
    public string customerName;
    [DataMember]
    public Collection<Item> items;
    [DataMember]
    public string[] comments;
}

[DataContract(Name = "PurchaseOrder")]
public class PurchaseOrder2
{
    [DataMember]
    public string customerName;
    [DataMember]
    public List<Item> items;
    [DataMember]
    public BindingList<string> comments;
}
<DataContract(Name:="PurchaseOrder")>
Public Class PurchaseOrder1

    <DataMember()>
    Public customerName As String

    <DataMember()>
    Public items As Collection(Of Item)

    <DataMember()>
    Public comments() As String

End Class

<DataContract(Name:="PurchaseOrder")>
Public Class PurchaseOrder2

    <DataMember()>
    Public customerName As String

    <DataMember()>
    Public items As List(Of Item)

    <DataMember()>
    Public comments As BindingList(Of String)

End Class

Båda datakontrakten resulterar i XML som liknar följande kod.

<PurchaseOrder>
    <customerName>...</customerName>
    <items>
        <Item>...</Item>
        <Item>...</Item>
        <Item>...</Item>
        ...
    </items>
    <comments>
        <string>...</string>
        <string>...</string>
        <string>...</string>
        ...
    </comments>
</PurchaseOrder>

Med samlingsutbyte kan du till exempel använda en samlingstyp som är optimerad för prestanda på servern och en samlingstyp som är utformad för att bindas till användargränssnittskomponenter på klienten.

På samma sätt som i listsamlingar anses alla ordlistesamlingar som har samma nyckel- och värdetyper ha samma datakontrakt (om de inte har anpassats av CollectionDataContractAttribute attributet).

Endast datakontraktstypen är viktig när det gäller insamlingens likvärdighet, inte .NET-typer. En samling av Type1 anses alltså motsvara en samling av Typ2 om Type1 och Type2 har motsvarande datakontrakt.

Icke-generiska samlingar anses ha samma datakontrakt som generiska samlingar av typen Object. (Datakontrakten för ArrayList och Generic List<T> för Object är till exempel desamma.)

Använda samlingsgränssnittstyper och skrivskyddade samlingar

Samlingsgränssnittstyper (IEnumerable, IDictionary, generiska IDictionary<TKey,TValue>eller gränssnitt som härleds från dessa gränssnitt) anses också ha samlingsdatakontrakt, vilket motsvarar insamling av datakontrakt för faktiska insamlingstyper. Därför är det möjligt att deklarera den typ som serialiseras som en samlingsgränssnittstyp och resultatet är detsamma som om en faktisk samlingstyp hade använts. Följande datakontrakt är till exempel likvärdiga.

[DataContract(Name="Customer")]
public class Customer1
{
    [DataMember]
    public string customerName;
    [DataMember]
    public Collection<Address> addresses;
}

[DataContract(Name="Customer")]
public class Customer2
{
    [DataMember]
    public string customerName;
    [DataMember]
    public ICollection<Address> addresses;
}
<DataContract(Name:="Customer")>
Public Class Customer1

    <DataMember()>
    Public customerName As String

    <DataMember()>
    Public addresses As Collection(Of Address)

End Class

<DataContract(Name:="Customer")>
Public Class Customer2

    <DataMember()>
    Public customerName As String

    <DataMember()>
    Public addresses As ICollection(Of Address)

End Class

När den deklarerade typen är ett gränssnitt under serialiseringen kan den faktiska instanstypen vara vilken typ som helst som implementerar gränssnittet. Begränsningar som diskuterats tidigare (med en parameterlös konstruktor och en Add metod) gäller inte. Du kan till exempel ange adresser i Customer2 till en instans av Generic ReadOnlyCollection<T> of Address, även om du inte direkt kan deklarera en datamedlem av typen Generic ReadOnlyCollection<T>.

När den deklarerade typen är ett gränssnitt under deserialiseringen väljer serialiseringsmotorn en typ som implementerar det deklarerade gränssnittet och typen instansieras. Mekanismen för kända typer (beskrivs i Kända typer av datakontrakt) har ingen effekt här. Valet av typ är inbyggt i WCF.

Anpassa samlingstyper

Du kan anpassa samlingstyper med hjälp CollectionDataContractAttribute av attributet, som har flera användningsområden.

Observera att anpassning av samlingstyper äventyrar samlingens utbytbarhet, så det rekommenderas vanligtvis att du undviker att använda det här attributet när det är möjligt. Mer information om det här problemet finns i avsnittet "Avancerade insamlingsregler" senare i det här avsnittet.

Namngivning av samlingsdatakontrakt

Reglerna för namngivning av samlingstyper liknar dem för namngivning av vanliga datakontraktstyper, enligt beskrivningen i Namn på datakontrakt, även om det finns några viktiga skillnader:

  • Attributet CollectionDataContractAttribute används för att anpassa namnet i stället för attributet DataContractAttribute . Attributet CollectionDataContractAttribute har Name också och Namespace egenskaper.

  • CollectionDataContractAttribute När attributet inte tillämpas beror standardnamnet och namnområdet för samlingstyper på namnen och namnrymderna för typer som ingår i samlingen. De påverkas inte av namnet och namnområdet för själva samlingstypen. Ett exempel finns i följande typer.

    public CustomerList1 : Collection<string> {}
    public StringList1 : Collection<string> {}
    

Båda typernas datakontraktsnamn är "ArrayOfstring" och inte "CustomerList1" eller "StringList1". Det innebär att serialisering av någon av dessa typer på rotnivå ger XML som liknar följande kod.

<ArrayOfstring>
    <string>...</string>
    <string>...</string>
    <string>...</string>
    ...
</ArrayOfstring>

Den här namngivningsregeln har valts för att säkerställa att alla icke-anpassade typer som representerar en lista med strängar har samma datakontrakt och XML-representation. Detta gör insamlingen utbytbar. I det här exemplet är CustomerList1 och StringList1 helt utbytbara.

Men när CollectionDataContractAttribute attributet tillämpas blir samlingen ett anpassat samlingsdatakontrakt, även om inga egenskaper har angetts för attributet. Namnet och namnområdet för samlingsdatakontraktet beror sedan på själva samlingstypen. Ett exempel finns i följande typ.

[CollectionDataContract]
public class CustomerList2 : Collection<string> {}
<CollectionDataContract()>
Public Class CustomerList2
    Inherits Collection(Of String)
End Class

När xml-koden serialiseras liknar den följande.

<CustomerList2>
    <string>...</string>
    <string>...</string>
    <string>...</string>
    ...
</CustomerList2>

Observera att detta inte längre motsvarar XML-representationen av de icke-anpassade typerna.

  • Du kan använda Name egenskaperna och Namespace för att ytterligare anpassa namngivningen. Se följande klass.

    [CollectionDataContract(Name="cust_list")]
    public class CustomerList3 : Collection<string> {}
    
    <CollectionDataContract(Name:="cust_list")>
    Public Class CustomerList3
        Inherits Collection(Of String)
    End Class
    

Den resulterande XML-koden liknar följande.

<cust_list>
    <string>...</string>
    <string>...</string>
    <string>...</string>
    ...
</cust_list>

Mer information finns i avsnittet "Avancerade insamlingsregler" senare i det här avsnittet.

Anpassa det upprepande elementnamnet i listsamlingar

Listsamlingar innehåller upprepade poster. Normalt representeras varje upprepande post som ett element med namnet enligt namnet på datakontraktet för den typ som ingår i samlingen.

I exemplen CustomerList innehöll samlingarna strängar. Namnet på datakontraktet för strängens primitiva typ är "string", så det upprepande elementet var "<string>".

Men med hjälp av ItemName egenskapen för CollectionDataContractAttribute attributet kan det här upprepande elementnamnet anpassas. Ett exempel finns i följande typ.

[CollectionDataContract(ItemName="customer")]
public class CustomerList4 : Collection<string>  {}
<CollectionDataContract(ItemName:="customer")>
Public Class CustomerList4
    Inherits Collection(Of String)
End Class

Den resulterande XML-koden liknar följande.

<CustomerList4>
    <customer>...</customer>
    <customer>...</customer>
    <customer>...</customer>
    ...
</CustomerList4>

Namnområdet för det upprepande elementet är alltid detsamma som namnområdet för samlingsdatakontraktet, som kan anpassas med hjälp av egenskapen, enligt beskrivningen Namespace tidigare.

Anpassa ordlistesamlingar

Ordlistesamlingar är i princip listor med poster, där varje post har en nyckel följt av ett värde. Precis som med vanliga listor kan du ändra det elementnamn som motsvarar det upprepande elementet med hjälp ItemName av egenskapen .

Dessutom kan du ändra de elementnamn som representerar nyckeln och värdet med hjälp KeyName av egenskaperna och ValueName . Namnrymderna för dessa element är samma som namnområdet för insamlingsdatakontraktet.

Ett exempel finns i följande typ.

[CollectionDataContract
    (Name = "CountriesOrRegionsWithCapitals",
    ItemName = "entry",
    KeyName = "countryorregion",
    ValueName = "capital")]
public class CountriesOrRegionsWithCapitals2 : Dictionary<string, string> { }
<CollectionDataContract(Name:="CountriesOrRegionsWithCapitals",
                        ItemName:="entry", KeyName:="countryorregion",
                        ValueName:="capital")>
Public Class CountriesOrRegionsWithCapitals2
    Inherits Dictionary(Of String, String)
End Class

När xml-koden serialiseras liknar den följande.

<CountriesOrRegionsWithCapitals>
    <entry>
        <countryorregion>USA</countryorregion>
        <capital>Washington</capital>
    </entry>
    <entry>
        <countryorregion>France</countryorregion>
        <capital>Paris</capital>
    </entry>
    ...
</CountriesOrRegionsWithCapitals>

Mer information om ordlistesamlingar finns i avsnittet "Avancerade samlingsregler" senare i det här avsnittet.

Samlingar och kända typer

Du behöver inte lägga till samlingstyper till kända typer när de används polymorft i stället för andra samlingar eller samlingsgränssnitt. Om du till exempel deklarerar en datamedlem av typen IEnumerable och använder den för att skicka en instans av ArrayListbehöver du inte lägga ArrayList till kända typer.

När du använder samlingar polymorfiskt i stället för icke-samlingstyper måste de läggas till i kända typer. Om du till exempel deklarerar en datamedlem av typen Object och använder den för att skicka en instans av ArrayListlägger du till ArrayList i kända typer.

På så sätt kan du inte serialisera motsvarande samling polymorfiskt. När du till exempel lägger till ArrayList i listan över kända typer i föregående exempel kan du inte tilldela Array of Object klassen, även om den har ett motsvarande datakontrakt. Detta skiljer sig inte från vanliga kända typers beteende vid serialisering för icke-samlingstyper, men det är särskilt viktigt att förstå i fallet med samlingar eftersom det är mycket vanligt att samlingar är likvärdiga.

Under serialiseringen kan endast en typ vara känd i ett visst omfång för ett visst datakontrakt, och motsvarande samlingar har alla samma datakontrakt. Det innebär att du i föregående exempel inte kan lägga till både ArrayList och Array of Object till kända typer i samma omfång. Detta motsvarar även kända typers beteende för icke-samlingstyper, men det är särskilt viktigt att förstå för samlingar.

Kända typer kan också krävas för innehållet i samlingar. Om en ArrayList faktiskt innehåller instanser av Type1 och Type2bör båda dessa typer läggas till i kända typer.

I följande exempel visas ett korrekt konstruerat objektdiagram med hjälp av samlingar och kända typer. Exemplet är något intrikat eftersom du i ett faktiskt program normalt inte skulle definiera följande datamedlemmar som Object, och därför inte har några kända problem med typ/polymorfism.

[DataContract]
public class Employee
{
    [DataMember]
    public string name = "John Doe";
    [DataMember]
    public Payroll payrollRecord;
    [DataMember]
    public Training trainingRecord;
}

[DataContract]
[KnownType(typeof(int[]))] //required because int[] is used polymorphically
[KnownType(typeof(ArrayList))] //required because ArrayList is used polymorphically
public class Payroll
{
    [DataMember]
    public object salaryPayments = new int[12];
    //float[] not needed in known types because polymorphic assignment is to another collection type
    [DataMember]
    public IEnumerable<float> stockAwards = new float[12];
    [DataMember]
    public object otherPayments = new ArrayList();
}

[DataContract]
[KnownType(typeof(List<object>))]
//required because List<object> is used polymorphically
//does not conflict with ArrayList above because it's a different scope,
//even though it's the same data contract
[KnownType(typeof(InHouseTraining))] //Required if InHouseTraining can be used in the collection
[KnownType(typeof(OutsideTraining))] //Required if OutsideTraining can be used in the collection
public class Training
{
    [DataMember]
    public object training = new List<object>();
}

[DataContract]
public class InHouseTraining
{
    //code omitted
}

[DataContract]
public class OutsideTraining
{
    //code omitted
}
<DataContract()>
Public Class Employee

    <DataMember()>
    Public name As String = "John Doe"

    <DataMember()>
    Public payrollRecord As Payroll

    <DataMember()>
    Public trainingRecord As Training

End Class

<DataContract(), KnownType(GetType(Integer())), KnownType(GetType(ArrayList))>
Public Class Payroll

    <DataMember()>
    Public salaryPayments As Object = New Integer(11) {}

    'float[] not needed in known types because polymorphic assignment is to another collection type
    <DataMember()>
    Public stockAwards As IEnumerable(Of Single) = New Single(11) {}

    <DataMember()>
    Public otherPayments As Object = New ArrayList()

End Class

'required because List<object> is used polymorphically
'does not conflict with ArrayList above because it's a different scope, 
'even though it's the same data contract
<DataContract(), KnownType(GetType(List(Of Object))),
                 KnownType(GetType(InHouseTraining)),
                 KnownType(GetType(OutsideTraining))>
Public Class Training
    <DataMember()>
    Public training As Object = New List(Of Object)()
End Class

<DataContract()>
Public Class InHouseTraining
    'code omitted…
End Class

<DataContract()>
Public Class OutsideTraining
    'code omitted…
End Class

Om den deklarerade typen är en samlingstyp vid deserialisering instansieras den deklarerade typen oavsett vilken typ som faktiskt skickades. Om den deklarerade typen är ett samlingsgränssnitt väljer deserialiseraren en typ som ska instansieras utan hänsyn till kända typer.

Även vid deserialisering, om den deklarerade typen inte är en samlingstyp men en samlingstyp skickas, väljs en matchande samlingstyp ut från listan över kända typer. Det är möjligt att lägga till samlingsgränssnittstyper i listan över kända typer vid deserialisering. I det här fallet väljer deserialiseringsmotorn igen en typ som ska instansieras.

Samlingar och NetDataContractSerializer-klassen

NetDataContractSerializer När klassen används förlorar icke-anpassade samlingstyper (utan CollectionDataContractAttribute attributet) som inte är matriser sin speciella betydelse.

Icke-anpassade samlingstyper som har markerats med SerializableAttribute attributet kan fortfarande serialiseras av NetDataContractSerializer klassen enligt SerializableAttribute attributet eller ISerializable gränssnittsreglerna.

Anpassade samlingstyper, samlingsgränssnitt och matriser behandlas fortfarande som samlingar, även när NetDataContractSerializer klassen används.

Samlingar och schema

Alla motsvarande samlingar har samma representation i XSD-schemat (XML Schema Definition Language). På grund av detta får du normalt inte samma samlingstyp i den genererade klientkoden som den på servern. Servern kan till exempel använda ett datakontrakt med en allmän List<T> heltalsdatamedlem, men i den genererade klientkoden kan samma datamedlem bli en matris med heltal.

Ordlistesamlingar markeras med en WCF-specifik schemaanteckning som anger att de är ordlistor. Annars kan de inte skiljas från enkla listor som innehåller poster med en nyckel och ett värde. En exakt beskrivning av hur samlingar representeras i datakontraktsschemat finns i Referens för datakontraktsschema.

Som standard genereras inte typer för icke-anpassade samlingar i importerad kod. Datamedlemmar i listsamlingstyper importeras som matriser och datamedlemmar i ordlistesamlingstyper importeras som allmän ordlista.

För anpassade samlingar genereras dock separata typer, markerade med CollectionDataContractAttribute attributet . (En anpassad samlingstyp i schemat är en som inte använder standardnamnområdet, namnet, det upprepande elementnamnet eller nyckel/värde-elementnamnen.) Dessa typer är tomma typer som härleds från Generic List<T> för listtyper och Generic Dictionary för ordlistetyper.

Du kan till exempel ha följande typer på servern.

[DataContract]
public class CountryOrRegion
{
    [DataMember]
    public Collection<string> officialLanguages;
    [DataMember]
    public List<DateTime> holidays;
    [DataMember]
    public CityList cities;
    [DataMember]
    public ArrayList otherInfo;
}

public class Person
{
    public Person(string fName, string lName)
    {
        this.firstName = fName;
        this.lastName = lName;
    }

    public string firstName;
    public string lastName;
}

public class PeopleEnum : IEnumerator
{
    public Person[] _people;

    // Enumerators are positioned before the first element
    // until the first MoveNext() call.
    int position = -1;

    public PeopleEnum(Person[] list)
    {
        _people = list;
    }

    public bool MoveNext()
    {
        position++;
        return (position < _people.Length);
    }

    public void Reset()
    {
        position = -1;
    }

    public object Current
    {
        get
        {
            try
            {
                return _people[position];
            }
            catch (IndexOutOfRangeException)
            {
                throw new InvalidOperationException();
            }
        }
    }
}

[CollectionDataContract(Name = "Cities", ItemName = "city", KeyName = "cityName", ValueName = "population")]
public class CityList : IDictionary<string, int>, IEnumerable<System.Collections.Generic.KeyValuePair<string, int>>
{
    private Person[] _people = null;

    public bool ContainsKey(string s) { return true; }
    public bool Contains(string s) { return true; }
    public bool Contains(KeyValuePair<string, int> item) { return (true); }
    public void Add(string key, int value) { }
    public void Add(KeyValuePair<string, int> keykValue) { }
    public bool Remove(string s) { return true; }
    public bool TryGetValue(string d, out int i)
    {
        i = 0; return (true);
    }

    /*
    [TypeConverterAttribute(typeof(SynchronizationHandlesTypeConverter))]
    public ICollection<string> SynchronizationHandles {
        get { return (System.Collections.Generic.ICollection<string>) new Stack<string> (); }
        set { }
    }*/

    public ICollection<string> Keys
    {
        get
        {
            return (System.Collections.Generic.ICollection<string>)new Stack<string>();
        }
    }

    public int this[string s]
    {
        get
        {
            return 0;
        }
        set
        {
        }
    }

    public ICollection<int> Values
    {
        get
        {
            return (System.Collections.Generic.ICollection<int>)new Stack<string>();
        }
    }

    public void Clear() { }
    public void CopyTo(KeyValuePair<string, int>[] array, int index) { }
    public bool Remove(KeyValuePair<string, int> item) { return true; }
    public int Count { get { return 0; } }
    public bool IsReadOnly { get { return true; } }

    IEnumerator<KeyValuePair<string, int>>
        System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, int>>.GetEnumerator()
    {
        return (IEnumerator<KeyValuePair<string, int>>)new PeopleEnum(_people); ;
    }

    public IEnumerator GetEnumerator()
    {
        return new PeopleEnum(_people);
    }
}

<DataContract()>
Public Class CountryOrRegion

    <DataMember()>
    Public officialLanguages As Collection(Of String)

    <DataMember()>
    Public holidays As List(Of DateTime)

    <DataMember()>
    Public cities As CityList

    <DataMember()>
    Public otherInfo As ArrayList

End Class

Public Class Person
    Public Sub New(ByVal fName As String, ByVal lName As String)
        Me.firstName = fName
        Me.lastName = lName
    End Sub

    Public firstName As String
    Public lastName As String
End Class

Public Class PeopleEnum
    Implements IEnumerator

    Public _people() As Person
    ' Enumerators are positioned before the first element
    ' until the first MoveNext() call.
    Private position As Integer = -1

    Public Sub New(ByVal list() As Person)
        _people = list
    End Sub

    Public Function MoveNext() As Boolean Implements IEnumerator.MoveNext
        position += 1
        Return position < _people.Length
    End Function

    Public Sub Reset() Implements IEnumerator.Reset
        position = -1
    End Sub

    Public ReadOnly Property Current() As Object Implements IEnumerator.Current
        Get
            Try
                Return _people(position)
            Catch e1 As IndexOutOfRangeException
                Throw New InvalidOperationException()
            End Try
        End Get
    End Property
End Class

<CollectionDataContract(Name:="Cities",
                        ItemName:="city",
                        KeyName:="cityName",
                        ValueName:="population")>
Public Class CityList
    Implements IDictionary(Of String, Integer), IEnumerable(Of System.Collections.Generic.KeyValuePair(Of String, Integer))

    Private _people() As Person = Nothing

    Public Function ContainsKey(ByVal s As String) As Boolean Implements IDictionary(Of String, Integer).ContainsKey
        Return True
    End Function

    Public Function Contains(ByVal s As String) As Boolean
        Return True
    End Function

    Public Function Contains(ByVal item As KeyValuePair(Of String, Integer)) As Boolean Implements IDictionary(Of String, Integer).Contains
        Return (True)
    End Function

    Public Sub Add(ByVal key As String,
                   ByVal value As Integer) Implements IDictionary(Of String, Integer).Add
    End Sub

    Public Sub Add(ByVal keykValue As KeyValuePair(Of String, Integer)) Implements IDictionary(Of String, Integer).Add
    End Sub

    Public Function Remove(ByVal s As String) As Boolean Implements IDictionary(Of String, Integer).Remove
        Return True
    End Function

    Public Function TryGetValue(ByVal d As String,
                                <System.Runtime.InteropServices.Out()> ByRef i As Integer) _
                                As Boolean Implements IDictionary(Of String, Integer).TryGetValue
        i = 0
        Return (True)
    End Function

    Public ReadOnly Property Keys() As ICollection(Of String) Implements IDictionary(Of String, Integer).Keys
        Get
            Return CType(New Stack(Of String)(), System.Collections.Generic.ICollection(Of String))
        End Get
    End Property

    Default Public Property Item(ByVal s As String) As Integer Implements IDictionary(Of String, Integer).Item
        Get
            Return 0
        End Get
        Set(ByVal value As Integer)
        End Set
    End Property

    Public ReadOnly Property Values() As ICollection(Of Integer) Implements IDictionary(Of String, Integer).Values
        Get
            Return CType(New Stack(Of String)(), System.Collections.Generic.ICollection(Of Integer))
        End Get
    End Property

    Public Sub Clear() Implements IDictionary(Of String, Integer).Clear
    End Sub

    Public Sub CopyTo(ByVal array() As KeyValuePair(Of String, Integer),
                      ByVal index As Integer) Implements IDictionary(Of String, Integer).CopyTo
    End Sub

    Public Function Remove(ByVal item As KeyValuePair(Of String, Integer)) As Boolean Implements IDictionary(Of String, Integer).Remove
        Return True
    End Function

    Public ReadOnly Property Count() As Integer Implements IDictionary(Of String, Integer).Count
        Get
            Return 0
        End Get
    End Property

    Public ReadOnly Property IsReadOnly() As Boolean Implements IDictionary(Of String, Integer).IsReadOnly
        Get
            Return True
        End Get
    End Property

    Private Function IEnumerable_GetEnumerator() As IEnumerator(Of KeyValuePair(Of String, Integer)) _
        Implements System.Collections.Generic.IEnumerable(Of System.Collections.Generic.KeyValuePair(Of String, Integer)).GetEnumerator

        Return CType(New PeopleEnum(_people), IEnumerator(Of KeyValuePair(Of String, Integer)))
    End Function

    Public Function GetEnumerator() As IEnumerator Implements System.Collections.IEnumerable.GetEnumerator

        Return New PeopleEnum(_people)

    End Function

End Class

När schemat exporteras och importeras tillbaka igen liknar den genererade klientkoden följande (fält visas i stället för egenskaper för att underlätta läsningen).

[DataContract]
public class CountryOrRegion2
{
    [DataMember]
    public string[] officialLanguages;
    [DataMember]
    public DateTime[] holidays;
    [DataMember]
    public Cities cities;
    [DataMember]
    public object[] otherInfo;
}

[CollectionDataContract(ItemName = "city", KeyName = "cityName", ValueName = "population")]
public class Cities : Dictionary<string, int> { }
<DataContract()>
Public Class CountryOrRegion2
    <DataMember()>
    Public officialLanguages() As String
    <DataMember()>
    Public holidays() As DateTime
    <DataMember()>
    Public cities As Cities
    <DataMember()>
    Public otherInfo() As Object
End Class

<CollectionDataContract(ItemName:="city", KeyName:="cityName", ValueName:="population")>
Public Class Cities
    Inherits Dictionary(Of String, Integer)
End Class

Du kanske vill använda olika typer i genererad kod än standardtyperna. Du kanske till exempel vill använda Generic BindingList<T> i stället för vanliga matriser för dina datamedlemmar för att göra det enklare att binda dem till komponenter i användargränssnittet.

Om du vill välja vilka samlingstyper som ska genereras skickar du en lista över samlingstyper som du vill använda till ReferencedCollectionTypes egenskapen för ImportOptions objektet när du importerar schemat. Dessa typer kallas refererade samlingstyper.

När generiska typer refereras måste de antingen vara helt öppna generiska eller helt stängda generiska objekt.

Kommentar

När du använder verktyget Svcutil.exe kan den här referensen utföras med kommandoradsväxeln /collectionType (kort formulär: /ct). Tänk på att du också måste ange sammansättningen för de refererade samlingstyperna med växeln /reference (kort formulär: /r). Om typen är generisk måste den följas av en bakåtcitat och antalet generiska parametrar. Det bakre citattecknet (') ska inte förväxlas med det enkla citattecknet ('). Du kan ange flera refererade samlingstyper med hjälp av växeln /collectionType mer än en gång.

Om du till exempel vill att alla listor ska importeras som generiska List<T>.

svcutil.exe MyService.wsdl MyServiceSchema.xsd /r:C:\full_path_to_system_dll\System.dll /ct:System.Collections.Generic.List`1

När du importerar en samling genomsöks den här listan med referenssamlingstyper och den bäst matchande samlingen används om en hittas, antingen som en datamedlemstyp (för icke-anpassade samlingar) eller som en bastyp att härleda från (för anpassade samlingar). Ordlistor matchas endast mot ordlistor, medan listor matchas mot listor.

Om du till exempel lägger till Generic BindingList<T> och Hashtable i listan över refererade typer liknar den genererade klientkoden för föregående exempel följande.

[DataContract]
public class CountryOrRegion3
{
    [DataMember]
    public BindingList<string> officialLanguages;
    [DataMember]
    public BindingList<DateTime> holidays;
    [DataMember]
    public Cities cities;
    [DataMember]
    public BindingList<object> otherInfo;
}

[CollectionDataContract(ItemName = "city", KeyName = "cityName", ValueName = "population")]
public class Cities3 : Hashtable { }
<DataContract()>
Public Class CountryOrRegion3

    <DataMember()>
    Public officialLanguages As BindingList(Of String)

    <DataMember()>
    Public holidays As BindingList(Of DateTime)

    <DataMember()>
    Public cities As Cities

    <DataMember()>
    Public otherInfo As BindingList(Of Object)

End Class

<CollectionDataContract(ItemName:="city",
                        KeyName:="cityName",
                        ValueName:="population")>
Public Class Cities3
    Inherits Hashtable
End Class

Du kan ange typer av samlingsgränssnitt som en del av dina refererade samlingstyper, men du kan inte ange ogiltiga samlingstyper Add (till exempel sådana utan metod eller offentlig konstruktor).

En sluten generisk anses vara den bästa matchningen. (Icke-generiska typer anses motsvara stängda generiska läkemedel av Object). Om till exempel typerna Generic List<T> av , Generic BindingList<T> (open generic) och ArrayList är de refererade samlingstyperna DateTimegenereras följande.

[DataContract]
public class CountryOrRegion4
{
    [DataMember]
    public string[] officialLanguages;
    [DataMember]
    public DateTime[] holidays;
    [DataMember]
    public Cities cities;
    [DataMember]
    public object[] otherInfo;
}

[CollectionDataContract(ItemName = "city", KeyName = "cityName", ValueName = "population")]
public class Cities4 : Dictionary<string, int> { }
<DataContract()>
Public Class CountryOrRegion4

    <DataMember()>
    Public officialLanguages() As String

    <DataMember()>
    Public holidays() As DateTime

    <DataMember()>
    Public cities As Cities

    <DataMember()>
    Public otherInfo() As Object

End Class

<CollectionDataContract(ItemName:="city",
                        KeyName:="cityName",
                        ValueName:="population")>
Public Class Cities4
    Inherits Dictionary(Of String, Integer)
End Class

För listsamlingar stöds endast fallen i följande tabell.

Refererad typ Gränssnitt implementerat efter refererad typ Exempel Typ som behandlas som:
Icke-generiska eller stängda generiska (valfritt antal parametrar) Icke-generisk MyType : IList

eller

MyType<T> : IList

där T= int
Sluten generisk av Object (till exempel IList<object>)
Icke-generiska eller stängda generiska (valfritt antal parametrar som inte nödvändigtvis matchar samlingstypen) Stängd allmän MyType : IList<string>

eller

MyType<T> : IList<string> där T=int
Stängd allmän (till exempel IList<string>)
Stängt generiskt med valfritt antal parametrar Öppna generisk med någon av typens parametrar MyType<T,U,V> : IList<U>

där T=int, U=string, V=bool
Stängd allmän (till exempel IList<string>)
Öppna generisk med en parameter Öppna generisk med hjälp av typens parameter MyType<T> : IList<T>, T är öppen Öppna generiska (till exempel IList<T>)

Om en typ implementerar fler än ett listsamlingsgränssnitt gäller följande begränsningar:

  • Om typen implementerar Generic IEnumerable<T> (eller dess härledda gränssnitt) flera gånger för olika typer anses typen inte vara en giltig referenssamlingstyp och ignoreras. Detta gäller även om vissa implementeringar är ogiltiga eller använder öppna generiska program. Till exempel skulle en typ som implementerar Generic IEnumerable<T> av int och Generic IEnumerable<T> of T aldrig användas som en refererad samling av int eller någon annan typ, oavsett om typen har en Add metod som accepterar int eller en Add metod som accepterar en parameter av typen T, eller båda.

  • Om typen implementerar ett generiskt samlingsgränssnitt samt IListanvänds typen aldrig som en refererad samlingstyp om inte det generiska samlingsgränssnittet är ett slutet generiskt av typen Object.

För ordlistesamlingar stöds endast ärendena i följande tabell.

Refererad typ Gränssnitt implementerat efter refererad typ Exempel Typ som behandlas som
Icke-generiska eller stängda generiska (valfritt antal parametrar) IDictionary MyType : IDictionary

eller

MyType<T> : IDictionary där T=int
Stängd allmän IDictionary<object,object>
Stängd allmän (valfritt antal parametrar) IDictionary<TKey,TValue>Stängt MyType<T> : IDictionary<string, bool> där T=int Stängd allmän (till exempel IDictionary<string,bool>)
Stängd allmän (valfritt antal parametrar) Allmän IDictionary<TKey,TValue>, en av antingen nyckeln eller värdet stängs, den andra är öppen och använder en av typens parametrar MyType<T,U,V> : IDictionary<string,V> where T=int, U=float,V=bool

eller

MyType<Z> : IDictionary<Z,bool> där Z=string
Stängd allmän (till exempel IDictionary<string,bool>)
Stängd allmän (valfritt antal parametrar) Allmän IDictionary<TKey,TValue>, både nyckel och värde är öppna och var och en använder en av typens parametrar MyType<T,U,V> : IDictionary<V,U> där T=int, U=bool, V=string Stängd allmän (till exempel IDictionary<string,bool>)
Öppna generisk (två parametrar) Generisk IDictionary<TKey,TValue>, öppen, använder båda av typens allmänna parametrar i den ordning de visas MyType<K,V> : IDictionary<K,V>, K och V båda öppna Öppna generiska (till exempel IDictionary<K,V>)

Om typen implementerar både IDictionary och Generic IDictionary<TKey,TValue>betraktas endast Generic IDictionary<TKey,TValue> .

Det går inte att referera till partiella generiska typer.

Dubbletter är till exempel inte tillåtna, du kan inte lägga till både Generic List<T> of Integer och Generic Collection of Integer i ReferencedCollectionTypes, eftersom det gör det omöjligt att avgöra vilken som ska användas när en lista med heltal hittas i schemat. Dubbletter identifieras endast om det finns en typ i schemat som exponerar dubbletter. Om det schema som importeras till exempel inte innehåller listor över heltal, tillåts det att ha både den generiska List<T>Integer och den generiska samlingen av i ReferencedCollectionTypes, men ingen av Integer dem har någon effekt.

Avancerade insamlingsregler

Serialisera samlingar

Följande är en lista över samlingsregler för serialisering:

  • Det är tillåtet att kombinera samlingstyper (med samlingar med samlingar). Ojämna matriser behandlas som samlingar med samlingar. Flerdimensionella matriser stöds inte.

  • Matriser med byte och matriser av XmlNode är särskilda matristyper som behandlas som primitiver, inte samlingar. Serialisering av en matris med byte resulterar i ett enda XML-element som innehåller ett segment med Base64-kodade data, i stället för ett separat element för varje byte. Mer information om hur en matris XmlNode med behandlas finns i XML och ADO.NET Typer i datakontrakt. Naturligtvis kan dessa specialtyper själva delta i samlingar: en matris med byte resulterar i flera XML-element, där var och en innehåller ett segment med Base64-kodade data.

  • DataContractAttribute Om attributet tillämpas på en samlingstyp behandlas typen som en vanlig datakontraktstyp, inte som en samling.

  • Om en samlingstyp implementerar IXmlSerializable gränssnittet gäller följande regler med en typ myType:IList<string>, IXmlSerializable:

    • Om den deklarerade typen är IList<string>serialiseras typen som en lista.

    • Om den deklarerade typen är myTypeserialiseras den som IXmlSerializable.

    • Om den deklarerade typen är IXmlSerializableserialiseras den som IXmlSerializable, men bara om du lägger till myType i listan över kända typer.

  • Samlingar serialiseras och deserialiseras med hjälp av metoderna som visas i följande tabell.

Implementeringar av samlingstyp Metoder som anropas för serialisering Metoder som anropas vid deserialisering
Generiska IDictionary<TKey,TValue> get_Keys, get_Values Allmänt tillägg
IDictionary get_Keys, get_Values Add
Generiska IList<T> Allmän IList<T> indexerare Allmänt tillägg
Generiska ICollection<T> Uppräknaren Allmänt tillägg
IList IList Indexerare Add
Generiska IEnumerable<T> GetEnumerator En icke-statisk metod med namnet Add som tar en parameter av lämplig typ (typen av den generiska parametern eller någon av dess bastyper). En sådan metod måste finnas för att serialiseraren ska kunna behandla en samlingstyp som en samling under både serialisering och deserialisering.
IEnumerable (och således ICollection, som härleds från den) GetEnumerator En icke-statisk metod med namnet Add som tar en parameter av typen Object. En sådan metod måste finnas för att serialiseraren ska kunna behandla en samlingstyp som en samling under både serialisering och deserialisering.

I föregående tabell visas samlingsgränssnitt i fallande ordning efter prioritet. Det innebär till exempel att om en typ implementerar både IList och Generic IEnumerable<T>, serialiseras och deserialiseras samlingen enligt IList reglerna:

  • Vid deserialisering deserialiseras alla samlingar genom att först skapa en instans av typen genom att anropa den parameterlösa konstruktorn, som måste finnas för serialiseraren för att behandla en samlingstyp som en samling under både serialisering och deserialisering.

  • Om samma allmänna samlingsgränssnitt implementeras mer än en gång (till exempel om en typ implementerar både Generisk ICollection<T> av Integer och Generisk ICollection<T> av String) och inget gränssnitt med högre prioritet hittas, behandlas samlingen inte som en giltig samling.

  • Samlingstyper kan ha attributet SerializableAttribute tillämpat på dem och kan implementera ISerializable gränssnittet. Båda dessa ignoreras. Men om typen inte helt uppfyller kraven för samlingstyp (till exempel Add om metoden saknas) betraktas inte typen som en samlingstyp, och därför SerializableAttribute används attributet och ISerializable gränssnittet för att avgöra om typen kan serialiseras.

  • CollectionDataContractAttribute Om du använder attributet för en samling för att anpassa det tar du bort den SerializableAttribute föregående återställningsmekanismen. Om en anpassad samling i stället inte uppfyller kraven för samlingstyp genereras ett InvalidDataContractException undantag. Undantagssträngen innehåller ofta information som förklarar varför en viss typ inte anses vara en giltig samling (ingen Add metod, ingen parameterlös konstruktor och så vidare), så det är ofta användbart att använda CollectionDataContractAttribute attributet för felsökning.

Namngivning av samling

Följande är en lista över namngivningsregler för samlingen:

  • Standardnamnområdet för alla ordlisteinsamlingsdatakontrakt, samt för listinsamlingsdatakontrakt som innehåller primitiva typer, åsidosätts http://schemas.microsoft.com/2003/10/Serialization/Arrays inte med namnområdet. Typer som mappar till inbyggda XSD-typer, samt char, Timespanoch Guid typer, betraktas som primitiver för det här ändamålet.

  • Standardnamnområdet för samlingstyper som innehåller icke-primitiva typer, såvida det inte åsidosätts med namnområdet, är samma som datakontraktets namnområde för den typ som ingår i samlingen.

  • Standardnamnet för listinsamlingsdatakontrakt, om det inte åsidosätts med namn, är strängen "ArrayOf" kombinerat med datakontraktets namn för den typ som ingår i samlingen. Till exempel är namnet på datakontraktet för en allmän lista över heltal "ArrayOfint". Tänk på att namnet på datakontraktet Object är "anyType", så datakontraktets namn på icke-generiska listor som ArrayList är "ArrayOfanyType".

Standardnamnet för ordlisteinsamlingsdatakontrakt, om de inte åsidosätts med , Nameär strängen "ArrayOfKeyValueOf" kombinerat med datakontraktets namn för nyckeltypen följt av datakontraktets namn för värdetypen. Till exempel är namnet på datakontraktet för en allmän ordlista med sträng och heltal "ArrayOfKeyValueOfstringint". Om antingen nyckeln eller värdetyperna inte är primitiva typer läggs dessutom en namnområdeshash för datakontraktets namnområden för nyckel- och värdetyperna till namnet. Mer information om namnområdeshashvärden finns i Namn på datakontrakt.

Varje ordlistesamlingsdatakontrakt har ett tillhörande datakontrakt som representerar en post i ordlistan. Namnet är detsamma som för ordlistedatakontraktet, förutom prefixet "ArrayOf" och dess namnområde är detsamma som för ordlistedatakontraktet. För ordlistedatakontraktet "ArrayOfKeyValueOfstringint" representerar datakontraktet "KeyValueofstringint" en post i ordlistan. Du kan anpassa namnet på det här datakontraktet ItemName med hjälp av egenskapen enligt beskrivningen i nästa avsnitt.

Namngivningsregler av allmän typ, enligt beskrivningen i Namn på datakontrakt, gäller helt för samlingstyper. Du kan alltså använda klammerparenteser i Namn för att ange parametrar av allmän typ. Tal inom klammerparenteserna refererar dock till allmänna parametrar och inte typer som finns i samlingen.

Anpassning av samling

Följande användningar av CollectionDataContractAttribute attributet är förbjudna och resulterar i ett InvalidDataContractException undantag:

Polymorfismregler

Som tidigare nämnts kan anpassning av samlingar med hjälp CollectionDataContractAttribute av attributet störa samlingens utbytbarhet. Två anpassade samlingstyper kan bara betraktas som likvärdiga om deras namn, namnområde, objektnamn samt nyckel- och värdenamn (om dessa är ordlistesamlingar) matchar.

På grund av anpassningar är det möjligt att oavsiktligt använda ett samlingsdatakontrakt där ett annat förväntas. Detta bör undvikas. Se följande typer.

[DataContract]
public class Student
{
    [DataMember]
    public string name;
    [DataMember]
    public IList<int> testMarks;
}
public class Marks1 : List<int> {}
[CollectionDataContract(ItemName="mark")]
public class Marks2 : List<int> {}
<DataContract()>
Public Class Student

    <DataMember()>
    Public name As String

    <DataMember()>
    Public testMarks As IList(Of Integer)

End Class

Public Class Marks1
    Inherits List(Of Integer)
End Class

<CollectionDataContract(ItemName:="mark")>
Public Class Marks2
    Inherits List(Of Integer)
End Class

I det här fallet kan en instans av Marks1 tilldelas till testMarks. Bör dock Marks2 inte användas eftersom dess datakontrakt inte anses vara likvärdigt med datakontraktet IList<int> . Namnet på datakontraktet är "Marks2" och inte "ArrayOfint", och det upprepande elementnamnet är "<mark>" och inte "<int>".

Reglerna i följande tabell gäller för polymorf tilldelning av samlingar.

Deklarerad typ Tilldela en icke-anpassad samling Tilldela en anpassad samling
Objekt Kontraktsnamnet serialiseras. Kontraktsnamnet serialiseras.

Anpassning används.
Samlingsgränssnitt Kontraktsnamnet serialiseras inte. Kontraktsnamnet serialiseras inte.

Anpassning används inte.*
Icke-anpassad samling Kontraktsnamnet serialiseras inte. Kontraktsnamnet serialiseras.

Anpassning används.**
Anpassad samling Kontraktsnamnet serialiseras. Anpassning används inte.** Kontraktsnamnet serialiseras.

Anpassning av den tilldelade typen används.**

*Med NetDataContractSerializer klassen används anpassning i det här fallet. Klassen NetDataContractSerializer serialiserar även det faktiska typnamnet i det här fallet, så deserialisering fungerar som förväntat.

**Dessa fall resulterar i schema-ogiltiga instanser och bör därför undvikas.

I de fall då kontraktsnamnet serialiseras ska den tilldelade samlingstypen finnas i listan över kända typer. Det motsatta är också sant: i de fall där namnet inte serialiseras krävs inte att du lägger till typen i listan över kända typer.

En matris av en härledd typ kan tilldelas till en matris av en bastyp. I det här fallet serialiseras kontraktsnamnet för den härledda typen för varje upprepande element. Om en typ Book till exempel härleds från typen LibraryItemkan du tilldela en matris Book till en matris med LibraryItem. Detta gäller inte för andra samlingstyper. Du kan till exempel inte tilldela en Generic List of Book till en Generic List of LibraryItem. Du kan dock tilldela en Generic List of LibraryItem som innehåller Book instanser. I både matrisen och fallet med icke-matrisen Book bör det finnas i listan med kända typer.

Samlingar och objektreferensbevarande

När en serialiserare fungerar i ett läge där den bevarar objektreferenser gäller även bevarande av objektreferenser för samlingar. Mer specifikt bevaras objektidentiteten för både hela samlingar och enskilda objekt som finns i samlingar. För ordlistor bevaras objektidentiteten både för nyckel-/värdeparobjekten och de enskilda nyckel- och värdeobjekten.

Se även