Можно ли сериализовать объект типа C#?

Я пытаюсь сериализовать объект типа следующим образом:

Type myType = typeof (StringBuilder);
var serializer = new XmlSerializer(typeof(Type));
TextWriter writer = new StringWriter();
serializer.Serialize(writer, myType);

При этом вызов сериализации вызывает следующее исключение:

«система типов.Текст.StringBuilder не ожидался. Используйте
Xmlinclude или SoapInclude атрибут для указания типов, которые не
известно статически.»

Есть ли способ сериализации Typeобъекта? Обратите внимание, что я не пытаюсь сериализовать саму StringBuilderсебя, но Typeобъект, содержащий метаданные о StringBuilderклассе.

6 ответов

  1. Просто посмотрел на его определение, оно не помечено как Сериализуемое. Если вам действительно нужно сериализовать эти данные, то вам, возможно, придется преобразовать их в пользовательский класс, который помечен как таковой.

    public abstract class Type : System.Reflection.MemberInfo
        Member of System
    
    Summary:
    Represents type declarations: class types, interface types, array types, value types, enumeration types, type parameters, generic type definitions, and open or closed constructed generic types.
    
    Attributes:
    [System.Runtime.InteropServices.ClassInterfaceAttribute(0),
    System.Runtime.InteropServices.ComDefaultInterfaceAttribute(System.Runtime.InteropServices._Type),
    System.Runtime.InteropServices.ComVisibleAttribute(true)]
    
  2. Согласно документации MSDN системы.Тип [1] Вы должны иметь возможность сериализовать систему.тип Object. Однако, поскольку ошибка явно относится к системе.Текст.StringBuilder-это скорее всего класс, вызывающий ошибку сериализации.

    [1] класс типа (System) — http://msdn.microsoft.com/en-us/library/system.type.aspx

  3. Я не знал, что объект Type может быть создан только со строкой, содержащей полное имя. Чтобы получить полное имя, можно использовать следующее:

    string typeName = typeof (StringBuilder).FullName;
    

    Затем вы можете сохранить эту строку, если это необходимо, а затем реконструировать тип следующим образом:

    Type t = Type.GetType(typeName);
    

    Если вам нужно создать экземпляр этого типа, вы можете сделать это:

    object o = Activator.CreateInstance(t);
    

    Если проверить значение o.GetType (), это будет StringBuilder, как и следовало ожидать.

  4. У меня была та же проблема, и моим решением было создать класс SerializableType. Оно свободно преобразовывает к и от системы.Тип, но сериализуется как строка. Все, что вам нужно сделать, это объявить переменную как SerializableType, и с этого момента вы можете ссылаться на нее как на System.Тип.

    Вот класс:

    // a version of System.Type that can be serialized
    [DataContract]
    public class SerializableType
    {
        public Type type;
    
        // when serializing, store as a string
        [DataMember]
        string TypeString
        {
            get
            {
                if (type == null)
                    return null;
                return type.FullName;
            }
            set
            {
                if (value == null)
                    type = null;
                else
                {
                    type = Type.GetType(value);
                }
            }
        }
    
        // constructors
        public SerializableType()
        {
            type = null;
        }
        public SerializableType(Type t)
        {
            type = t;
        }
    
        // allow SerializableType to implicitly be converted to and from System.Type
        static public implicit operator Type(SerializableType stype)
        {
            return stype.type;
        }
        static public implicit operator SerializableType(Type t)
        {
            return new SerializableType(t);
        }
    
        // overload the == and != operators
        public static bool operator ==(SerializableType a, SerializableType b)
        {
            // If both are null, or both are same instance, return true.
            if (System.Object.ReferenceEquals(a, b))
            {
                return true;
            }
    
            // If one is null, but not both, return false.
            if (((object)a == null) || ((object)b == null))
            {
                return false;
            }
    
            // Return true if the fields match:
            return a.type == b.type;
        }
        public static bool operator !=(SerializableType a, SerializableType b)
        {
            return !(a == b);
        }
        // we don't need to overload operators between SerializableType and System.Type because we already enabled them to implicitly convert
    
        public override int GetHashCode()
        {
            return type.GetHashCode();
        }
    
        // overload the .Equals method
        public override bool Equals(System.Object obj)
        {
            // If parameter is null return false.
            if (obj == null)
            {
                return false;
            }
    
            // If parameter cannot be cast to SerializableType return false.
            SerializableType p = obj as SerializableType;
            if ((System.Object)p == null)
            {
                return false;
            }
    
            // Return true if the fields match:
            return (type == p.type);
        }
        public bool Equals(SerializableType p)
        {
            // If parameter is null return false:
            if ((object)p == null)
            {
                return false;
            }
    
            // Return true if the fields match:
            return (type == p.type);
        }
    }
    

    и пример использования:

    [DataContract]
    public class A
    {
    
        ...
    
        [DataMember]
        private Dictionary<SerializableType, B> _bees;
    
        ...
    
        public B GetB(Type type)
        {
            return _bees[type];
        }
    
        ...
    
    }
    

    Можно также использовать AssemblyQualifiedName вместо Type.FullName-смотрите комментарий @GreyCloud

  5. Ответ Брайана хорошо работает, если тип находится в той же сборке, что и вызов (как GreyCloud указал в одном из комментариев).
    Поэтому, если тип находится в другой сборке, необходимо использовать AssemblyQualifiedName, Как также указано GreyCloud.

    Тем не менее, как AssemblyQualifiedName сохраняет версию, если ваши сборки имеют другую версию, чем в строке, где у вас есть тип, он не будет работать.

    В моем случае это была проблема, и я решил ее так:

    string typeName = typeof (MyClass).FullName;
    
    Type type = GetTypeFrom(typeName);
    
    object myInstance = Activator.CreateInstance(type);
    

    Метод GetTypeFrom

    private Type GetTypeFrom(string valueType)
        {
            var type = Type.GetType(valueType);
            if (type != null)
                return type;
    
            try
            {
                var assemblies = AppDomain.CurrentDomain.GetAssemblies();                
    
                //To speed things up, we check first in the already loaded assemblies.
                foreach (var assembly in assemblies)
                {
                    type = assembly.GetType(valueType);
                    if (type != null)
                        break;
                }
                if (type != null)
                    return type;
    
                var loadedAssemblies = assemblies.ToList();
    
                foreach (var loadedAssembly in assemblies)
                {
                    foreach (AssemblyName referencedAssemblyName in loadedAssembly.GetReferencedAssemblies())
                    {
                        var found = loadedAssemblies.All(x => x.GetName() != referencedAssemblyName);
    
                        if (!found)
                        {
                            try
                            {
                                var referencedAssembly = Assembly.Load(referencedAssemblyName);
                                type = referencedAssembly.GetType(valueType);
                                if (type != null)
                                    break;
                                loadedAssemblies.Add(referencedAssembly);
                            }
                            catch
                            {
                                //We will ignore this, because the Type might still be in one of the other Assemblies.
                            }
                        }
                    }
                }                
            }
            catch(Exception exception)
            {
                //throw my custom exception    
            }
    
            if (type == null)
            {
                //throw my custom exception.
            }
    
            return type;
        }
    

    Я публикую это на случай, если кому-то это нужно.

  6. Я столкнулся с этой проблемой, пытаясь сделать двоичную сериализацию в .net standard 2.0. В итоге я решил проблему с помощью пользовательского SurrogateSelectorИ.SerializationBinder

    Это TypeSerializationBinderбыло необходимо, потому что фреймворк имел проблемы с разрешениемSystem.RuntimeType, прежде чем он получил SurrogateSelector. Я действительно не понимаю, почему тип должен быть решен до этого шага…

    Вот код:

    // Serializes and deserializes System.Type
    public class TypeSerializationSurrogate : ISerializationSurrogate {
        public void GetObjectData(object obj, SerializationInfo info, StreamingContext context) {
            info.AddValue(nameof(Type.FullName), (obj as Type).FullName);
        }
    
        public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector) {
            return Type.GetType(info.GetString(nameof(Type.FullName)));
        }
    }
    
    // Just a stub, doesn't need an implementation
    public class TypeStub : Type { ... }
    
    // Binds "System.RuntimeType" to our TypeStub
    public class TypeSerializationBinder : SerializationBinder {
        public override Type BindToType(string assemblyName, string typeName) {
            if(typeName == "System.RuntimeType") {
                return typeof(TypeStub);
            }
            return Type.GetType($"{typeName}, {assemblyName}");
        }
    }
    
    // Selected out TypeSerializationSurrogate when [de]serializing Type
    public class TypeSurrogateSelector : ISurrogateSelector {
        public virtual void ChainSelector(ISurrogateSelector selector) => throw new NotSupportedException();
    
        public virtual ISurrogateSelector GetNextSelector() => throw new NotSupportedException();
    
        public virtual ISerializationSurrogate GetSurrogate(Type type, StreamingContext context, out ISurrogateSelector selector) {
            if(typeof(Type).IsAssignableFrom(type)) {
                selector = this;
                return new TypeSerializationSurrogate();
            }
            selector = null;
            return null;
        }
    }
    

    пример использования:

    byte[] bytes
    var serializeFormatter = new BinaryFormatter() {
        SurrogateSelector = new TypeSurrogateSelector()
    }
    using (var stream = new MemoryStream()) {
        serializeFormatter.Serialize(stream, typeof(string));
        bytes = stream.ToArray();
    }
    
    var deserializeFormatter = new BinaryFormatter() {
        SurrogateSelector = new TypeSurrogateSelector(),
        Binder = new TypeDeserializationBinder()
    }
    using (var stream = new MemoryStream(bytes)) {
        type = (Type)deserializeFormatter .Deserialize(stream);
        Assert.Equal(typeof(string), type);
    }