質問
次の方法で Type オブジェクトをシリアル化しようとしています。
Type myType = typeof (StringBuilder);
var serializer = new XmlSerializer(typeof(Type));
TextWriter writer = new StringWriter();
serializer.Serialize(writer, myType);
これを行うと、Serialize の呼び出しにより次の例外がスローされます。
「System.Text.StringBuilder 型は予期されていませんでした。xmlincludeまたはsoapinclude属性を使用して、静的に知られていないタイプを指定します。」
シリーズ化する方法はありますか Type
物体?シリアル化しようとしているわけではないことに注意してください。 StringBuilder
それ自体ですが、 Type
に関するメタデータを含むオブジェクト StringBuilder
クラス。
解決
Type オブジェクトは完全修飾名を含む文字列のみを使用して作成できることを知りませんでした。完全修飾名を取得するには、次を使用できます。
string typeName = typeof (StringBuilder).FullName;
その後、必要に応じてこの文字列を永続化し、次のように型を再構築できます。
Type t = Type.GetType(typeName);
このタイプのインスタンスを作成する必要がある場合は、次のように実行できます。
object o = Activator.CreateInstance(t);
o.GetType() の値を確認すると、ご想像のとおり、StringBuilder になっています。
他のヒント
私も同じ問題を抱えていましたが、解決策は SerializableType クラスを作成することでした。System.Type との間で自由に変換できますが、文字列としてシリアル化されます。変数を SerializableType として宣言するだけで、それ以降は System.Type として参照できるようになります。
クラスは次のとおりです。
// 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];
}
...
}
Type.FullName の代わりに AssemblyQualifiedName を使用することも検討してください - @GreyCloud によるコメントを参照してください
ブライアンの 型が呼び出しと同じアセンブリ内にある場合、答えはうまく機能します(コメントの1つで指摘されたGreyCloudのように)。したがって、型が別のアセンブリにある場合は、 アセンブリ修飾名 GreyCloudも指摘したように。
ただし、 アセンブリ修飾名 バージョンを保存します。アセンブリのバージョンが、型が指定されている文字列内のバージョンと異なる場合、機能しません。
私の場合、これが問題でしたので、次のように解決しました。
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;
}
誰かが必要とする場合に備えてこれを投稿します。
System.Type の MSDN ドキュメント [1] によると、System.Type オブジェクトをシリアル化できるはずです。ただし、エラーは System.Text.StringBuilder を明示的に参照しているため、それがシリアル化エラーの原因となっている可能性があります。
[1] 型クラス (システム) - http://msdn.microsoft.com/en-us/library/system.type.aspx
その定義を見たところ、シリアル化可能としてマークされていません。このデータをシリアル化する必要がある場合は、そのようにマークされたカスタム クラスにデータを変換する必要がある場合があります。
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)]
.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);
}