如何在C#中检查一个对象是否可序列化

96
我正在寻找一种简便的方法来检查 C# 中的对象是否可序列化。
我们知道,您可以通过实现 ISerializable 接口或在类顶部放置 [Serializable] 来使对象可序列化。
我正在寻找一种快速检查此属性的方法,而无需反射该类以获取其属性。使用 is 语句可以快速使用接口。
使用 @Flard 的建议,这是我想出的代码,请告诉我是否有更好的方法。
private static bool IsSerializable(T obj)
{
    return ((obj is ISerializable) || (Attribute.IsDefined(typeof (T), typeof (SerializableAttribute))));
}

或者更好的方法是获取对象的类型,然后使用该类型上的IsSerializable属性:

typeof(T).IsSerializable

请记住,虽然我们只涉及到这个类,但如果这个类包含其他类,您可能需要检查所有类或尝试序列化并等待错误,就像@pb指出的那样。


1
抱歉,当 obj 中的某个字段不可序列化时会失败,请参考我的示例。 - Paul van Brenk
我认为这是一个更好的方法:https://dev59.com/W3VC5IYBdhLWcg3wnCj6#236698 - xero
“你可以通过实现ISerializable接口或在类顶部放置[Serializable]来使对象可序列化”这个说法是错误的。为了使对象可序列化,它的类必须声明SerializableAttribute。实现ISerializable只能让你对过程有更多的控制。 - Mishax
9个回答

124

您在Type类上有一个名为IsSerializable的可爱属性。


9
这只是告诉您是否将Serializable属性附加到了您的类。 - Fatema
39
他的观点是,虽然包含该对象的类型是可序列化的,但该对象的成员可能并不可序列化。对吗?难道我们不需要递归地深入到该对象的每个成员中并检查每一个成员,如果不能序列化,就尝试序列化它并查看是否失败吗? - Brian Sweeney
4
例如,对于List<SomeDTO>,即使SomeDTO不可序列化,IsSerializable仍为true。 - Simon Dowdeswell

43

你需要检查被序列化对象的图中所有类型是否具有可序列化属性。最简单的方法是尝试序列化对象并捕获异常。(但这不是最干净的解决方案) Type.IsSerializable和检查可序列化属性不考虑图形。

[Serializable]
public class A
{
    public B B = new B();
}

public class B
{
   public string a = "b";
}

[Serializable]
public class C
{
    public D D = new D();
}

[Serializable]
public class D
{
    public string d = "D";
}


class Program
{
    static void Main(string[] args)
    {

        var a = typeof(A);

        var aa = new A();

        Console.WriteLine("A: {0}", a.IsSerializable);  // true (WRONG!)

        var c = typeof(C);

        Console.WriteLine("C: {0}", c.IsSerializable); //true

        var form = new BinaryFormatter();
        // throws
        form.Serialize(new MemoryStream(), aa);
    }
}

如果成本不太大,我认为这种方法是最好的。它可以检查不同的序列化要求(二进制、XML)。此外,一个对象可能有一个通用成员,可以与继承类类型交换,这可能会破坏序列化并且在运行时可能会发生变化。List(Of baseclass) 可以添加子类A的项,而子类A是不可序列化的,其中 baseclass 和 subclassB 是可序列化的。 - VoteCoffee
这个答案使用克隆来检查序列化是否可以往返。但在某些情况下,这可能过于复杂了,因为不需要序列化设置某些成员变量:https://dev59.com/W3VC5IYBdhLWcg3wnCj6#236698 - VoteCoffee

18

这是一个旧问题,可能需要针对.NET 3.5+进行更新。如果类使用DataContract属性,则Type.IsSerializable实际上可能返回false。 这里是我使用的代码片段,如果不好,请告诉我:)

public static bool IsSerializable(this object obj)
{
    Type t = obj.GetType();

     return  Attribute.IsDefined(t, typeof(DataContractAttribute)) || t.IsSerializable || (obj is IXmlSerializable)

}

1
虽然这是一个老问题,但它非常真实!Type.IsSerializable只是部分功能的解决方案。事实上,考虑到现在有多少人使用WCF和DataContracts,它实际上是一个非常糟糕的解决方案! - Jaxidian
如果 obj 为空怎么办? - N73k
@N73k,进行null检查,如果为true则返回? - FredM

9

正如其他人指出的那样,使用Type.IsSerializable。

尝试反射并检查对象图中的所有成员是否可序列化可能并不值得。因为一个成员可以被声明为可序列化类型,但实际上被实例化为一个不可序列化的派生类型,就像下面这个假想的例子:

[Serializable]
public class MyClass
{
   public Exception TheException; // serializable
}

public class MyNonSerializableException : Exception
{
...
}

...
MyClass myClass = new MyClass();
myClass.TheException = new MyNonSerializableException();
// myClass now has a non-serializable member

因此,即使您确定您类型的特定实例是可序列化的,一般情况下也不能确定所有实例都是可序列化的。

6
Attribute.IsDefined(typeof (YourClass), typeof (SerializableAttribute));

可能涉及水下反射,但最简单的方法是什么?

5
这是一个3.5版本的变化,通过扩展方法使其适用于所有类。
public static bool IsSerializable(this object obj)
{
    if (obj is ISerializable)
        return true;
    return Attribute.IsDefined(obj.GetType(), typeof(SerializableAttribute));
}

2
我把这个问题的答案和这里的答案结合起来,修改了一下,让你可以得到一个不可序列化类型的列表。这样你就可以轻松知道哪些需要标记了。
    private static void NonSerializableTypesOfParentType(Type type, List<string> nonSerializableTypes)
    {
        // base case
        if (type.IsValueType || type == typeof(string)) return;

        if (!IsSerializable(type))
            nonSerializableTypes.Add(type.Name);

        foreach (var propertyInfo in type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
        {
            if (propertyInfo.PropertyType.IsGenericType)
            {
                foreach (var genericArgument in propertyInfo.PropertyType.GetGenericArguments())
                {
                    if (genericArgument == type) continue; // base case for circularly referenced properties
                    NonSerializableTypesOfParentType(genericArgument, nonSerializableTypes);
                }
            }
            else if (propertyInfo.GetType() != type) // base case for circularly referenced properties
                NonSerializableTypesOfParentType(propertyInfo.PropertyType, nonSerializableTypes);
        }
    }

    private static bool IsSerializable(Type type)
    {
        return (Attribute.IsDefined(type, typeof(SerializableAttribute)));
        //return ((type is ISerializable) || (Attribute.IsDefined(type, typeof(SerializableAttribute))));
    }

然后你把它叫做...
    List<string> nonSerializableTypes = new List<string>();
    NonSerializableTypesOfParentType(aType, nonSerializableTypes);

当它运行时,nonSerializableTypes 将拥有该列表。可能有比将空列表传递给递归方法更好的方法。如果有,请有人纠正我。

1
我的解决方案是VB.NET:
对于对象:
''' <summary>
''' Determines whether an object can be serialized.
''' </summary>
''' <param name="Object">The object.</param>
''' <returns><c>true</c> if object can be serialized; otherwise, <c>false</c>.</returns>
Private Function IsObjectSerializable(ByVal [Object] As Object,
                                      Optional ByVal SerializationFormat As SerializationFormat =
                                                                            SerializationFormat.Xml) As Boolean

    Dim Serializer As Object

    Using fs As New IO.MemoryStream

        Select Case SerializationFormat

            Case Data.SerializationFormat.Binary
                Serializer = New Runtime.Serialization.Formatters.Binary.BinaryFormatter()

            Case Data.SerializationFormat.Xml
                Serializer = New Xml.Serialization.XmlSerializer([Object].GetType)

            Case Else
                Throw New ArgumentException("Invalid SerializationFormat", SerializationFormat)

        End Select

        Try
            Serializer.Serialize(fs, [Object])
            Return True

        Catch ex As InvalidOperationException
            Return False

        End Try

    End Using ' fs As New MemoryStream

End Function

对于类型:

''' <summary>
''' Determines whether a Type can be serialized.
''' </summary>
''' <typeparam name="T"></typeparam>
''' <returns><c>true</c> if Type can be serialized; otherwise, <c>false</c>.</returns>
Private Function IsTypeSerializable(Of T)() As Boolean

    Return Attribute.IsDefined(GetType(T), GetType(SerializableAttribute))

End Function

''' <summary>
''' Determines whether a Type can be serialized.
''' </summary>
''' <typeparam name="T"></typeparam>
''' <param name="Type">The Type.</param>
''' <returns><c>true</c> if Type can be serialized; otherwise, <c>false</c>.</returns>
Private Function IsTypeSerializable(Of T)(ByVal Type As T) As Boolean

    Return Attribute.IsDefined(GetType(T), GetType(SerializableAttribute))

End Function

1
我喜欢这个,因为正如其他回答者所指出的那样,属性和特性毫无意义。你可以在任何类上贴上ISerializable属性,Type.IsSerializable是有缺陷的,但最重要的是:它所引用的对象可能实际上是不可序列化的子类。唯一的方法就是尝试。但我会将其改进为TrySerialize函数,以便成功序列化不需要重复执行。 - Wolfzoon

0
异常对象可能可序列化,但使用另一个异常则不行。 这就是我在 WCF System.ServiceModel.FaultException 中遇到的问题:FaultException 可序列化,但 ExceptionDetail 则不能!
因此我正在使用以下内容:
// Check if the exception is serializable and also the specific ones if generic
var exceptionType = ex.GetType();
var allSerializable = exceptionType.IsSerializable;
if (exceptionType.IsGenericType)
    {
        Type[] typeArguments = exceptionType.GetGenericArguments();
        allSerializable = typeArguments.Aggregate(allSerializable, (current, tParam) => current & tParam.IsSerializable);
    }
 if (!allSerializable)
    {
        // Create a new Exception for not serializable exceptions!
        ex = new Exception(ex.Message);
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接