2010-07-26 13 views
6

Estoy tratando de implementar un contrato de servicio que contiene un método que toma una interfaz genérica, y esa interfaz genérica en sí tiene un parámetro de interfaz. Me he decorado la interfaz de servicio con ServiceKnownType, he decorado con la implementación del servicio KnownType regular, y he decorado la aplicación DataContract con KnownType normal:WCF: ¿Es posible la serialización de interfaces genéricas?

[ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(ICallbacks))] 
[ServiceKnownType(typeof(Batch<object>))] 
[ServiceKnownType(typeof(Command))] 
public interface IActions 
{ 
    [OperationContract] 
    IResponse TakeAction(IBatch<ICommand> commands); 
} 

[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Reentrant)] 
[KnownType(typeof(Batch<object>))] 
[KnownType(typeof(Command))] 
internal class Actions : IActions 
{ 
} 

[DataContract] 
[KnownType(typeof(Command))] 
public class Batch<T> : IBatch<T> 
{ 
} 

Para el registro, no tengo por lotes, ya que parece que solo puede expresar un tipo de conocimiento para un tipo genérico una vez: parece emitir BatchOfanyType, pero no estoy seguro de cómo manejarlo.

La excepción que obtengo es "Agregue cualquier tipo no conocido estáticamente a la lista de tipos conocidos, por ejemplo, utilizando el atributo KnownTypeAttribute o agregándolos a la lista de tipos conocidos pasados ​​a DataContractSerializer".

¿Hay algo obvio que estoy haciendo mal? ¿Las interfaces genéricas de las interfaces simplemente no son compatibles? Para el registro estoy en C# 2.0 y .NET 3.0 para este proyecto.

Respuesta

12

Puede usar interfaces en las definiciones de contrato de servicio si realmente lo desea, siempre que incluya los tipos conocidos como lo hace (con un ligero ajuste, consulte a continuación).

Aparentemente, utilizar una interfaz como parámetro de tipo genérico es llevarlo demasiado lejos para C# 3.0. Cambié el atributo de tipo conocido a

[ServiceKnownType(typeof(Batch<Command>))] 
public interface IActions 
{ 
} 

Lo que hace que funcione, hasta cierto punto. Serialización y deserialización sí va a funcionar, pero luego se enfrenta a esta excepción:

No se puede convertir objeto de tipo 'Batch`1 [Comando]' al tipo 'IBatch`1 [ICommand]'.

Para que funcione, necesita compatibilidad con el lenguaje para la covarianza de tipo genérico, algo que se introdujo en C# 4.0. Para que funcione en C# 4.0, sin embargo, que había necesidad de añadir un modificador de la varianza:

public interface IBatch<out T> 
{ 
} 

entonces funciona perfectamente ... por desgracia no se está usando C# 4.0.

Una última cosa sobre el uso de interfaces en su contrato de servicio: si está generando una referencia de servicio de ellos, escribirá todos los parámetros de interfaz como object, porque el tipo de interfaz original no es parte de los metadatos. Puede compartir contratos a través de una referencia de ensamblado o refactorizar manualmente el proxy generado para arreglarlo, pero en general, usar interfaces con WCF probablemente sea más problemático de lo que vale.

+0

Sí, edité en la plataforma que estoy usando cuando pensé en la covarianza en C# 4.0. Oh, para actualizar. – bwerks

2

WCF es un sistema basado en mensajes SOA - puede enviar cualquier cosa a través del cable en el formato XML serializado que se puede expresar en el esquema XML.

Desafortunadamente, el esquema XML no sabe nada ni interfaces ni genéricos, entonces no, no puede serializarlos genéricamente, necesita usar tipos concretos.

+0

WCF hace entender genéricos suficiente para serializarlos (lo he hecho) Su punto en Interfaces es correcto. – RQDQ

+0

@RQDQ ¿tiene un lugar para mostrar eso? ¿Un blog, un artículo de CodeProject o algo así? Me encanta ver eso! –

+1

@RQDQ: En realidad, no es del todo correcto, imo. Si bien la interfaz en sí no se puede serializar, el tipo concreto utilizado * puede * ser. La parte "engañosa" es la deserialización, porque necesita saber qué tipo concreto instanciar. Sin embargo, se incluye suficiente información en los datos serializados para hacer exactamente eso. – Thorarin

1

No se puede serializar una interfaz. Una interfaz solo define el contrato, no el objeto. Supongo que la única excepción a esto es la interfaz ISerializable.

+1

Cuando "serializa' ISerializable' ", realmente no lo es. 'ISerializable' es una interfaz utilizada para serializar algo más. –

+0

@John. Estás en lo correcto. Todavía no está serializando realmente la interfaz, solo acordando un contrato utilizado para serializar los objetos que implementan ISerializable. Acabo de agregar esto para completar. – Mike

1

Los genéricos se pueden serializar, pero con ciertas limitaciones.Por ejemplo, dado el contrato de datos:

[DataContract] 
public class Foo<T> 
{ 
    [DataMember] 
    public T Value { get; set; } 
} 

Y el contrato de servicios:

[ServiceContract] 
public interface IService1 
{ 
    [OperationContract] 
    Foo<String> GetData(); 
} 

Y la implementación del servicio:

public class Service1 : IService1 
{ 
    public Foo<string> GetData() 
    { 
     return new Foo<string>() { Value = "My test string" }; 
    } 
} 

Después de establecer una referencia de servicio para el servicio anteriormente, este el código se puede ejecutar:

ServiceReference1.Service1Client client = new ServiceReference1.Service1Client(); 

ServiceReference1.FooOfstring temp = client.GetData(); 

MessageBox.Show(temp.Value); 

Y se muestra el cuadro de mensaje con "Mi cadena de prueba".

Tenga en cuenta que el servicio en sí no es genérico, pero el contrato de datos utilizado es. Además, el contrato de datos generada en el lado del cliente no es genérica, sino más bien una clase de "aplanado" que tiene un valor de propiedad de tipo cadena:

[System.Runtime.Serialization.DataMemberAttribute()] 
public string Value 
{ 
    get {...} 
    set {...} 
} 
Cuestiones relacionadas