Section 3.10. Collections


3.10. Collections

In .NET, a collection is any type that supports the IEnumerable or IEnumerable<T> interfaces. All of the built-in collections in .NET, such as the array, the list, and the stack support these interfaces. A data contract can include a collection as a data member, or a service contract can define operations that interact with a collection directly. Because .NET collections are .NET-specific, WCF cannot expose them in the service metadata, yet because they are so useful, WCF offers dedicated marshaling rules for collections.

Whenever you're defining a service operation that uses any of the following collection interfaces: IEnumerable<T>, IList<T>, and ICollection<T>, the wire representation always uses an array. For example, this service contract definition and implementation:

 [ServiceContract] interface IContactManager {    [OperationContract]    IEnumerable<Contact> GetContacts( );    ... } class ContactManager : IContactManager {    List<Contact> m_Contacts = new List<Contact>( );    public IEnumerable<Contact> GetContacts( )    {       return m_Contacts;    }    ... } 

will be exported as:

 [ServiceContract] interface IContactManager {    [OperationContract]    Contact[] GetContacts( ); } 

3.10.1. Concrete Collections

If the collection in the contract is a concrete collection (not an interface), and is a serializable collectionthat is, it is marked with the Serializable attribute but not with the DataContract attributeWCF can normalize the collection automatically to an array of the collection's type, provided the collection contains an Add( ) method with either one of these signatures:

 public void Add(object obj); //Collection uses IEnumerable public void Add(T item);     //Collection uses IEnumerable<T> 

For example, consider this contract definition:

 [ServiceContract] interface IContactManager {    [OperationContract]    void AddContact(Contact contact);    [OperationContract]    List<Contact> GetContacts( ); } 

The list class is defined as:

 public interface ICollection<T> : IEnumerable<T> {...} public interface IList<T> : ICollection<T> {...} [Serializable] public class List<T> : IList<T> {    public void Add(T item);    //More members } 

Because it is a valid collection and it has an Add( ) method, the resulting wire representation of the contract will be:

 [ServiceContract] interface IContactManager {    [OperationContract]    void AddContact(Contact contact);    [OperationContract]    Contact[] GetContacts( ); } 

That is, the List<Contacts> is marshaled as a Contact[]. The service may still return a List<Contacts>, and yet the client will interact with an array, as shown in Example 3-14.

Example 3-14. Marshaling a list as an array

 /////////////////////////// Service Side ////////////////////////////// [ServiceContract] interface IContactManager {    [OperationContract]    void AddContact(Contact contact);    [OperationContract]    List<Contact> GetContacts( ); } //Service implementation class ContactManager : IContactManager {    List<Contact> m_Contacts = new List<Contact>( );    public void AddContact(Contact contact)    {       m_Contacts.Add(contact);    }    public List<Contact> GetContacts( )    {       return m_Contacts;    } } /////////////////////////// Client Side ////////////////////////////// [ServiceContract] interface IContactManager {    [OperationContract]    void AddContact(Contact contact);    [OperationContract]    Contact[] GetContacts( ); } public partial class ContactManagerClient : ClientBase<IContactManager>,                                                                     IContactManager {    public Contact[] GetContacts( )    {       return Channel.GetContacts( );    } } //Client code ContactManagerClient proxy = new ContactManagerClient( ); Contact[] contacts = proxy.GetContacts( ); proxy.Close( ); 

Note that while the collection must have the Add( ) method for it to be marshaled as an array, the collection need not implement the Add( ) method at all.

3.10.2. Custom Collections

The ability to automatically marshal a collection as an array is not limited to the built-in collections. Any custom collection can abide by the same prerequisites and be marshaled as an array, as shown in Example 3-15. In the example, the collection MyCollection<string> is marshaled as a string[].

Example 3-15. Marshaling a custom collection as an array

 /////////////////////////// Service Side ////////////////////////////// [Serializable] public class MyCollection<T> : IEnumerable<T> {    public void Add(T item)    {}    IEnumerator<T>  IEnumerable<T>.GetEnumerator( )    {...}    //Rest of the implementation } [ServiceContract] interface IMyContract {    [OperationContract]    MyCollection<string> GetCollection( ); } /////////////////////////// Client Side ////////////////////////////// [ServiceContract] interface IMyContract {    [OperationContract]    string[] GetCollection( ); } 

3.10.3. Collection Data Contract

The mechanism shown so far for marshaling a concrete collection is suboptimal. First, it requires the collection to be serializable, and does not work with the service-oriented DataContract attribute. While one party is dealing with a collection, the other is dealing with an array. The two are not semantically equivalentthe collection is likely to offer some advantages, or it would not have been used in the first place. There is no compile-time or run-time verification of the presence of the Add( ) method or the IEnumerable and IEnumerable<T> interfaces, resulting in an unworkable data contract if they are missing. The solution is yet another dedicated attribute called CollectionDataContractAttribute, defined as:

 [AttributeUsage(AttributeTargets.Struct|AttributeTargets.Class,Inherited = false)] public sealed class CollectionDataContractAttribute : Attribute {    public string Name    {get;set;}    public string Namespace    {get;set;}    //More members } 

CollectionDataContract attribute is analogous to the DataContract attribute, and it does not make the collection serializable. When applied on a collection, the CollectionDataContract attribute exposes the collection to the client as a generic linked list. While the linked list may have nothing to do with the original collection, it does offer a more collection-like interface than an array.

For example, given this collection definition:

 [CollectionDataContract(Name = "MyCollectionOf{0}")] public class MyCollection<T> : IEnumerable<T> {    public void Add(T item)    {}    IEnumerator<T>  IEnumerable<T>.GetEnumerator( )    {...}    //Rest of the implementation } 

and this service-side contract definition:

 [ServiceContract] interface IContactManager {    [OperationContract]    void AddContact(Contact contact);    [OperationContract]    MyCollection<Contact> GetContacts( ); } 

the definitions the client ends up with after importing the metadata will be:

 [CollectionDataContract] public class MyCollectionOfContact : List<Contact> {} [ServiceContract] interface IContactManager {    [OperationContract]    void AddContact(Contact contact);    [OperationContract]    MyCollectionOfContact GetContacts( ); } 

In addition, the CollectionDataContract attribute verifies at the service load time the presence of the Add( ) method as well as IEnumerable or IEnumerable<T>. Failing to have these on the collection will result in an InvalidDataContractException.

Note that you cannot apply both the DataContract attribute and CollectionDataContract attribute on the collection, and again this is verified at the service load time.

3.10.4. Referencing the Collection

WCF can even let you preserve the same collection on the client side as on the service side. The SvcUtil utility offers the /collectionType switch (or /ct for short), allowing you to reference a particular collection assembly on the client side and have it be used in the contract definition. You need to specify the location of the collection assembly, and the assembly must of course be available to the client.

For example, the service could define the following contract that makes use of the Stack<T> collection:

 [ServiceContract] interface IContactManager {    [OperationContract]    void AddContact(Contact contact);    [OperationContract]    Stack<Contact> GetContacts( ); } 

The client points SvcUtil at the service metadata exchange address (such as http://localhost:8000), uses the /r switch to reference the System.dll assembly containing the Stack<t> class, and the /ct switch to indicate it wants to preserve the original collection:

 SvcUtil http://localhost:8000/         /r:C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\System.dll         /ct:System.Collections.Generic.Stack'1 

The resulting client-side contract definition will be using Stack<t>:

 [ServiceContract] interface IContactManager {    [OperationContract]    void AddContact(Contact contact);    [OperationContract]    Stack<Contact> GetContacts( ); } 

Obviously, the use of /rct switch is not very service-oriented. It requires intimate knowledge beforehand of the collection used, and it works only in WCF-to-WCF interactions.

3.10.5. Client-Side Collection

Interacting with collections so far was discussed in the context of the service defining and using a collection, while letting the client interact with either an array or a list. As it turns out, it can actually work the other way around: the service can be defined in terms of an array, and the client can use a compatible collection.

For example, consider this service contract definition:

 [ServiceContract] interface IMyContract {    [OperationContract]    void ProcessArray(string[] array); } 

By default, the imported service and proxy definitions will be identical. However, the client can manually rework the contract and the proxy to use any collection interface:

 //Reworked definition: [ServiceContract] interface IMyContract {    [OperationContract]    void ProcessArray(IList<string> list); } 

and supply at call time a collection with the Add( ) method (serializable or with the CollectionDataContract attribute):

 IList<string> list = new List<string>( ); MyContractClient proxy = new MyContractClient( ); proxy.ProcessArray(list); proxy.Close( ); 

3.10.6. C# Iterators

The iterators'[*] feature of C# 2.0 lets you rely on the compiler to generate the implementation of a custom iterator on a collection. However, that implementation is done on a nested class that is not marked with the Serializable attribute. Consequently, you cannot return that collection directly from a service method:

[*] If you are unfamiliar with C# 2.0 iterators, see my MSDN Magazine article "Create Elegant Code with Anonymous Methods, Iterators, and Partial Classes," May 2004.

 [ServiceContract] interface IContactManager {    [OperationContract]    IEnumerable<Contact> GetContacts( );    ... } class ContactManager : IContactManager {    List<Contact> m_Contacts = new List<Contact>( );    //Invalid implementation    public IEnumerable<Contact> GetContacts( )    {       foreach(Contact contact in m_Contacts)       {          yield return contact;       }    }    ... } 

In the next release of C# (along with the rest of .NET 3.5) the compiler will add the Serializable attribute to the nested class generated by the yield return statement, thus enabling returning an iterator directly from a service method.


3.10.7. Dictionaries

Dictionaries are a special type of a collection that maps one data contract type to another. As such, they do not fit well either as an array or as a list. Not surprisingly, dictionaries get their own representation in WCF.

If the dictionary is a serializable collection that supports the IDictionary interface, then it will be exposed as a Dictionary<object,object>. For example, this service contract definition:

 [Serializable] public class MyDictionary : IDictionary {...} [ServiceContract] interface IContactManager {    ...    [OperationContract]    MyDictionary GetContacts( ); } 

will be exposed as this definition:

 [ServiceContract] interface IContactManager {    ...    [OperationContract]    Dictionary<object,object> GetContacts( ); } 

This, by the way, includes using the HashTable collection.

If the serializable collection supports the IDictionary<K,T> interface, such as:

 [Serializable] public class MyDictionary<K,T> : IDictionary<K,T> {...} [ServiceContract] interface IContactManager {    ...    [OperationContract]    MyDictionary<int,Contact> GetContacts( ); } 

then its wire representation will be as a Dictionary<K,T>:

 [ServiceContract] interface IContactManager {    ...    [OperationContract]    Dictionary<int,Contact> GetContacts( ); } 

This includes making direct use of the Dictionary<K,T> in the original definition.

If instead of a mere serializable collection the dictionary is decorated with the CollectionDataContract, it will be marshaled as a subclass of the respective representation. For example, this service contract definition:

 [CollectionDataContract] public class MyDictionary : IDictionary {...} [ServiceContract] interface IContactManager {    ...    [OperationContract]    MyDictionary GetContacts( ); } 

will have this wire representation:

 [CollectionDataContract] public class MyDictionary : Dictionary<object,object> {} [ServiceContract] interface IContactManager {    ...    [OperationContract]    MyDictionary GetContacts( ); } 

while this generic collection:

 [CollectionDataContract] public class MyDictionary<K,T> : IDictionary<K,T> {...} [ServiceContract] interface IContactManager {    ...    [OperationContract]    MyDictionary<int,Contact> GetContacts( ); } 

will be published in the metadata as:

 [CollectionDataContract] public class MyDictionary : Dictionary<int,Contact> {} [ServiceContract] interface IContactManager {    ...    [OperationContract]    MyDictionary GetContacts( ); } 




Programming WCF Services
Programming WCF Services
ISBN: 0596526997
EAN: 2147483647
Year: 2004
Pages: 148
Authors: Juval Lowy

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net