为每个派生类避免 KnownType 属性的公认方法

Posted

技术标签:

【中文标题】为每个派生类避免 KnownType 属性的公认方法【英文标题】:Generally accepted way to avoid KnownType attribute for every derived class 【发布时间】:2013-04-19 16:07:22 【问题描述】:

是否有一种普遍接受的方法来避免在 WCF 服务上使用 KnownType 属性?我一直在做一些研究,看起来有两种选择:

    Data contract resolver NetDataContractSerializer

我不喜欢每次添加新类型时都必须静态添加 KnownType 属性,因此想避免它。

是否应该使用第三个选项?如果是这样,它是什么?如果不是,以上两个选项中哪个是正确的选择?

编辑 - 使用方法

第三种选择是使用反射

[DataContract]
[KnownType("DerivedTypes")]
public abstract class FooBase

    private static Type[] DerivedTypes()
    
        return typeof(FooBase).GetDerivedTypes(Assembly.GetExecutingAssembly()).ToArray();
    

【问题讨论】:

【参考方案1】:

我想发布迄今为止我能想到的最简单、最优雅的解决方案。如果出现另一个更好的答案,我会同意的。但就目前而言,这运作良好。

基类,只有一个 KnownType属性,指向一个名为DerivedTypes()的方法:

[KnownType("DerivedTypes")]
[DataContract]
public abstract class TaskBase : EntityBase

    // other class members here

    private static Type[] DerivedTypes()
    
        return typeof(TaskBase).GetDerivedTypes(Assembly.GetExecutingAssembly()).ToArray();
    

GetDerivedTypes() 方法,在单独的 ReflectionUtility 类中:

public static IEnumerable<Type> GetDerivedTypes(this Type baseType, Assembly assembly)

    var types = from t in assembly.GetTypes()
                where t.IsSubclassOf(baseType)
                select t;

    return types;

【讨论】:

如果你不想创建扩展方法,这可以变成一个单行。 return Assembly.GetExecutingAssembly().GetTypes().Where(_ =&gt; _.IsSubclassOf(typeof(TaskBase))).ToArray(); 这是一个很好的解决方案。干净简单。【参考方案2】:

只要所有涉及的类都在同一个程序集中,Bob 提到的方法就可以工作。

以下方法适用于程序集:

[DataContract]
[KnownType("GetDerivedTypes")]
public class BaseClass

  public static List<Type> DerivedTypes = new List<Type>();

  private static IEnumerable<Type> GetDerivedTypes()
  
    return DerivedTypes;
  



[DataContract]
public class DerivedClass : BaseClass

  //static constructor
  static DerivedClass()
  
    BaseClass.DerivedTypes.Add(typeof(DerivedClass)); 
  

【讨论】:

+1 是一个不错的选择。请注意,反射选项也可以跨程序集工作。我喜欢这个,因为它很干净。我能想到的唯一负面因素是派生类必须记住实现这个静态构造函数。有了反射,开发人员的记忆中就没有任何东西了。 是的,但是对于许多程序集,性能可以成为一个考虑因素。这种方法的另一个缺点是必须公开类型的公共列表。 您还需要确保有一些东西可以调用静态构造函数:我相信,CLR 运行时不会在加载程序集时运行程序集中的每个静态构造函数,仅在第一次加载该类时以某种方式访问​​或使用。【参考方案3】:

这是我接受的答案的变体:

    private static IEnumerable<Type> GetKnownTypes() 
        Type baseType = typeof(MyBaseType);
        return AppDomain.CurrentDomain.GetAssemblies()
            .SelectMany(x => x.DefinedTypes)
            .Where(x => x.IsClass && !x.IsAbstract && x.GetCustomAttribute<DataContractAttribute>() != null && baseType.IsAssignableFrom(x));
    

区别在于:

    查看所有加载的程序集。 检查我们感兴趣的一些位(如果您使用 DataContractJsonSerializer,我认为需要 DataContract),例如作为具体类。 您可以在此处使用 isSubclassOf,我一般倾向于使用 IsAssignableFrom 来捕获所有被覆盖的变体。特别是我认为它适用于泛型。 利用 KnownTypes 接受 IEnumerable(如果在这种情况下很重要,可能不重要)而不是转换为数组。

【讨论】:

【参考方案4】:

您可以在自定义类型中实现 IXmlSerializable 并手动处理其复杂性。 您可以在下面找到示例代码:

[XmlRoot("ComplexTypeA")]
public class ComplexTypeA : IXmlSerializable

    public int Value  get; set; 

    public void WriteXml (XmlWriter writer)
    
        writer.WriteAttributeString("Type", this.GetType().FullName);
        writer.WriteValue(this.Value.ToString());
    

    public void ReadXml (XmlReader reader)
    
        reader.MoveToContent();
        if (reader.HasAttributes) 
            if (reader.GetAttribute("Type") == this.GetType().FullName) 
                this.Value = int.Parse(reader.ReadString());
            
        
    

    public XmlSchema GetSchema()
    
        return(null);
    


[XmlRoot("ComplexTypeB")]
public class ComplexTypeB : IXmlSerializable

    public string Value  get; set; 

    public void WriteXml (XmlWriter writer)
    
        writer.WriteAttributeString("Type", this.GetType().FullName);
        writer.WriteValue(this.Value);
    

    public void ReadXml (XmlReader reader)
    
        reader.MoveToContent();
        if (reader.HasAttributes) 
            if (reader.GetAttribute("Type") == this.GetType().FullName) 
                this.Value = reader.ReadString();
            
        
    

    public XmlSchema GetSchema()
    
        return(null);
    



[XmlRoot("ComplexTypeC")]
public class ComplexTypeC : IXmlSerializable

    public Object ComplexObj  get; set; 

    public void WriteXml (XmlWriter writer)
    
        writer.WriteAttributeString("Type", this.GetType().FullName);
        if (this.ComplexObj != null)
        
            writer.WriteAttributeString("IsNull", "False");
            writer.WriteAttributeString("SubType", this.ComplexObj.GetType().FullName);
            if (this.ComplexObj is ComplexTypeA)
            
                writer.WriteAttributeString("HasValue", "True");
                XmlSerializer serializer = new XmlSerializer(typeof(ComplexTypeA));
                serializer.Serialize(writer, this.ComplexObj as ComplexTypeA);
            
            else if (tthis.ComplexObj is ComplexTypeB)
            
                writer.WriteAttributeString("HasValue", "True");
                XmlSerializer serializer = new XmlSerializer(typeof(ComplexTypeB));
                serializer.Serialize(writer, this.ComplexObj as ComplexTypeB);
            
            else
            
                writer.WriteAttributeString("HasValue", "False");
            
        
        else
        
            writer.WriteAttributeString("IsNull", "True");
        
    

    public void ReadXml (XmlReader reader)
    
        reader.MoveToContent();
        if (reader.HasAttributes) 
            if (reader.GetAttribute("Type") == this.GetType().FullName) 
                if ((reader.GetAttribute("IsNull") == "False") && (reader.GetAttribute("HasValue") == "True")) 
                    if (reader.GetAttribute("SubType") == typeof(ComplexTypeA).FullName)
                    
                        XmlSerializer serializer = new XmlSerializer(typeof(ComplexTypeA));
                        this.ComplexObj = serializer.Deserialize(reader) as ComplexTypeA;
                    
                    else if (reader.GetAttribute("SubType") == typeof(ComplexTypeB).FullName)
                    
                        XmlSerializer serializer = new XmlSerializer(typeof(ComplexTypeB));
                        this.ComplexObj = serializer.Deserialize(reader) as ComplexTypeB;
                    
                
            
        
    

    public XmlSchema GetSchema()
    
        return(null);
    

希望对你有帮助。

【讨论】:

谢谢@Farzan,但就维护而言,这似乎超出了我的预期。在我的问题中,我的第三个选项允许我仅在基类上实现一个调用,并节省大量编码。很高兴看到您的方法,但它只是需要维护更多代码。【参考方案5】:

如果你不喜欢到处都有属性,那么你可以使用配置文件。

<system.runtime.serialization>
   <dataContractSerializer>
      <declaredTypes>
         <add type = "Contact,Host,Version=1.0.0.0,Culture=neutral,
                                                              PublicKeyToken=null">
            <knownType type = "Customer,MyClassLibrary,Version=1.0.0.0,
                                             Culture=neutral,PublicKeyToken=null"/>
         </add>
      </declaredTypes>
   </dataContractSerializer>
</system.runtime.serialization>

【讨论】:

感谢蒂姆的提示。但是,我的主要目标是不必每次有新类型时都在某处更新列表。很高兴知道。 同样,这将要求您在每次您的程序集版本更改时更新您的配置。【参考方案6】:

我宁愿一次性提取所有自定义类型并在序列化/反序列化期间使用它。读完这篇文章后,我花了一段时间才明白在哪里注入这个类型列表以对序列化器对象有用。答案很简单:这个列表将用作序列化器对象的构造函数的输入参数之一。

1- 我正在使用两种静态通用方法进行序列化和反序列化,这可能或多或少是其他人的工作方式,或者至少与您的代码进行比较非常清楚:

    public static byte[] Serialize<T>(T obj)
    
        var serializer = new DataContractSerializer(typeof(T), MyGlobalObject.ResolveKnownTypes());
        var stream = new MemoryStream();
        using (var writer =
            XmlDictionaryWriter.CreateBinaryWriter(stream))
        
            serializer.WriteObject(writer, obj);
        
        return stream.ToArray();
    
    public static T Deserialize<T>(byte[] data)
    
        var serializer = new DataContractSerializer(typeof(T), MyGlobalObject.ResolveKnownTypes());
        using (var stream = new MemoryStream(data))
        using (var reader =
            XmlDictionaryReader.CreateBinaryReader(
                stream, XmlDictionaryReaderQuotas.Max))
        
            return (T)serializer.ReadObject(reader);
        
    

2- 请注意DataContractSerializer的构造函数。我们在那里有第二个参数,它是将已知类型注入序列化器对象的入口点。

3- 我使用静态方法从我自己的程序集中提取我自己定义的所有类型。此静态方法的代码可能如下所示:

    private static Type[] KnownTypes  get; set; 
    public static Type[] ResolveKnownTypes()
    
        if (MyGlobalObject.KnownTypes == null)
        
            List<Type> t = new List<Type>();
            List<AssemblyName> c = System.Reflection.Assembly.GetEntryAssembly().GetReferencedAssemblies().Where(b => b.Name == "DeveloperCode" | b.Name == "Library").ToList();
            foreach (AssemblyName n in c)
            
                System.Reflection.Assembly a = System.Reflection.Assembly.Load(n);
                t.AddRange(a.GetTypes().ToList());
            
            MyGlobalObject.KnownTypes = t.ToArray();
        
        return IOChannel.KnownTypes;
    

由于我没有参与 WCF(我只需要二进制序列化进行文件操作),我的解决方案可能无法完全解决 WCF 架构,但必须从某个地方访问序列化程序对象的构造函数。

【讨论】:

以上是关于为每个派生类避免 KnownType 属性的公认方法的主要内容,如果未能解决你的问题,请参考以下文章

无法从Base转换为Derived

wcf - 多个KnowType命令 - 执行的正确模式

在 SvcUtil 和 WSDL 代码生成期间抓取 KnownType 属性

将基类转换为派生类[重复]

WCF 派生类型和违反 Open/Closed 原则

为啥我不能在我的 WCF 类中使用 KnownType 属性?