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( );
}
If the collection in the contract is a concrete collection (not an interface), and is a serializable collection—that is, it is marked with the Serializable
attribute but not with the DataContract
attribute—WCF 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.
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( );
}
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 equivalent—the 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.
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:WINDOWSMicrosoft.NETFrameworkv2.0.50727System.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.
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 = newList
<string>( );
MyContractClient proxy = new MyContractClient( );
proxy.ProcessArray(list);
proxy.Close( );
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:
[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;
}
}
...
}
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( );
}
[*] 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.