使用 DataContractJsonSerializer,将 JSON 字符串反序列化为具有列表和接口作为属性的 C# 对象
Using DataContractJsonSerializer, deserialization of JSON string into C# object which has list & interface as properties
我正在开发一个调用第 3 方 REST 服务的 c#.dotNet 项目。
样本Class结构:
[Serializable]
[DataContract(Name = "MainClass")]
[KnownType(typeof(Class1))]
[KnownType(typeof(Class2))]
public class MainClass : IMainInterface
{
public MainClass()
{
Value2 = new List<IClass2>();
}
[DataMember(Name = "class")]
public IClass1 Value1 { get; set; }
[DataMember(Name = "classes")]
public List<IClass2> Value2 { get; set; }
}
[Serializable]
[Export(typeof(IClass1))]
[ExportMetadata("IClass1", "Class1")]
[DataContract(Name = "class1")]
public class Class1 : IClass1
{
[ImportingConstructor]
public Class1()
{
}
[DataMember(Name = "prop1")]
public string Prop1 { get; set; }
[DataMember(Name = "prop2")]
public string Prop2 { get; set; }
[DataMember(Name = "prop3")]
public string Prop3 { get; set; }
}
[Serializable]
[Export(typeof(IClass2))]
[ExportMetadata("IClass2", "Class2")]
[DataContract]
public class Class2 : IClass2
{
[ImportingConstructor]
public Class2()
{ }
[DataMember(Name = "propA")]
public string PropA { get; set; }
[DataMember(Name = "propB")]
public string PropB { get; set; }
[DataMember(Name = "propC")]
public string PropC { get; set; }
}
public interface IMainInterface
{
IClass1 Value1 { get; set; }
List<IClass2> Value2 { get; set; }
}
public interface IClass1
{
string Prop1 { get; set; }
string Prop2 { get; set; }
string Prop3 { get; set; }
}
public interface IClass2
{
string PropA { get; set; }
string PropB { get; set; }
string PropC { get; set; }
}
Json_String1:(带有类型提示)
{
"class":
{"__type":"class1:#WpfApplication1","prop1":"TestVal0","prop2":"TestVal2","prop3":"TestVal3"},
"classes":
[
{"__type":"Class2:#WpfApplication1","propA":"A","propB":"B","propC":"C"},
{"__type":"Class2:#WpfApplication1","propA":"X","propB":"Y","propC":"Z"},
{"__type":"Class2:#WpfApplication1","propA":"1","propB":"2","propC":"3"}
]
}
Json_String2:(无类型提示)
{
"class":
{"prop1":"TestVal0","prop2":"TestVal2","prop3":"TestVal3"},
"classes":
[
{"propA":"A","propB":"B","propC":"C"},
{"propA":"X","propB":"Y","propC":"Z"},
{"propA":"1","propB":"2","propC":"3"}
]
}
因此,对于给定的 class 结构,如果我使用 DataContractJsonSerializer
生成 json(MainClass
的对象),我将得到 Json_String1 如果我直接反序列化,它工作正常。
而当 GET 获取数据时,响应是 Json_String2(w/o 类型提示)。因此,在反序列化时出现以下错误。
InvalidCastException was unhandled. Unable to cast object of type
'System.Object' to type 'WpfApplication1.IClass2'.
现在,我必须通过添加类型提示手动修改 json(字符串操作),才能成功反序列化它。
问题 1) 如何避免这种 Json 字符串操作反序列化?
问题 2) 如何在没有类型提示的情况下创建 json?
编辑:
1. 新增IMainInterface,由MainClass实现。
2. 点网框架 4
问题是因为您试图反序列化为只有接口的 class。当 JSON 指定 __type 时,它显然有效。但是当它不存在时(就像在您的第二个 JSON 示例中一样),ReadObject 无法自动将接口解析为其实现。
尝试使用 MainClass
中的具体 classes Class1
、Class2
而不是它们的接口(IClass1、IClass2)。其余代码可以保持原样。您的两个 Json 示例都可以正常工作。
[Serializable]
[DataContract(Name = "MainClass")]
[KnownType(typeof(Class1))]
[KnownType(typeof(Class2))]
public class MainClass
{
public MainClass()
{
Value2 = new List<Class2>();
}
[DataMember(Name = "class")]
public Class1 Value1 { get; set; }
[DataMember(Name = "classes")]
public List<Class2> Value2 { get; set; }
}
由于 none 的 classes 实际上是多态的,因此有几个解决方案可用,它们使用 .Net 内置 class 库:
方案一:JavaScriptSerializer
方案
JavaScriptSerializer
makes it easy to remap interfaces to classes during deserialization by using a JavaScriptConverter
。但是,它不允许重新映射字段和 属性 名称,因此您的 属性 名称 必须与 [=49] 中的名称相匹配=] 您希望处理 。以下转换器可以解决问题:
public class InterfaceToClassConverter<TInterface, TClass> : JavaScriptConverter where TClass : class, TInterface
{
public InterfaceToClassConverter()
{
if (typeof(TInterface) == typeof(TClass))
throw new ArgumentException(string.Format("{0} and {1} must not be the same type", typeof(TInterface).FullName, typeof(TClass).FullName)); // Or else you would get infinite recursion!
if (!typeof(TInterface).IsInterface)
throw new ArgumentException(string.Format("{0} must be an interface", typeof(TInterface).FullName));
if (typeof(TClass).IsInterface)
throw new ArgumentException(string.Format("{0} must be a class not an interface", typeof(TClass).FullName));
}
public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
{
if (type == typeof(TInterface))
return serializer.ConvertToType<TClass>(dictionary);
return null;
}
public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
{
throw new NotImplementedException();
}
public override IEnumerable<Type> SupportedTypes
{
get
{
// For an interface-valued property such as "IFoo Foo { getl set; },
// When serializing, JavaScriptSerializer knows the actual concrete type being serialized -- which is never an interface.
// When deserializing, JavaScriptSerializer only knows the expected type, which is an interface. Thus by returning
// only typeof(TInterface), we ensure this converter will only be called during deserialization, not serialization.
return new[] { typeof(TInterface) };
}
}
}
并像这样使用它:
public interface IMainInterface
{
IClass1 @class { get; set; } // NOTICE ALL PROPERTIES WERE RENAMED TO MATCH THE JSON NAMES.
List<IClass2> classes { get; set; }
}
[Serializable]
[DataContract(Name = "MainClass")]
[KnownType(typeof(Class1))]
[KnownType(typeof(Class2))]
public class MainClass : IMainInterface
{
public MainClass()
{
classes = new List<IClass2>();
}
[DataMember(Name = "class")]
public IClass1 @class { get; set; }
[DataMember(Name = "classes")]
public List<IClass2> classes { get; set; }
}
[Serializable]
[DataContract(Name = "class1")]
public class Class1 : IClass1
{
public Class1() {}
[DataMember(Name = "prop1")]
public string prop1 { get; set; }
[DataMember(Name = "prop2")]
public string prop2 { get; set; }
[DataMember(Name = "prop3")]
public string prop3 { get; set; }
}
[Serializable]
[DataContract]
public class Class2 : IClass2
{
public Class2() { }
[DataMember(Name = "propA")]
public string propA { get; set; }
[DataMember(Name = "propB")]
public string propB { get; set; }
[DataMember(Name = "propC")]
public string propC { get; set; }
}
public interface IClass1
{
string prop1 { get; set; }
string prop2 { get; set; }
string prop3 { get; set; }
}
public interface IClass2
{
string propA { get; set; }
string propB { get; set; }
string propC { get; set; }
}
public static class TestJavaScriptConverter
{
public static void Test()
{
string json = @"
{
""class"":
{""prop1"":""TestVal0"",""prop2"":""TestVal2"",""prop3"":""TestVal3""},
""classes"":
[
{""propA"":""A"",""propB"":""B"",""propC"":""C""},
{""propA"":""X"",""propB"":""Y"",""propC"":""Z""},
{""propA"":""1"",""propB"":""2"",""propC"":""3""}
]
}";
var serializer = new JavaScriptSerializer();
serializer.RegisterConverters(new JavaScriptConverter[] { new InterfaceToClassConverter<IClass1, Class1>(), new InterfaceToClassConverter<IClass2, Class2>() });
var main1 = serializer.Deserialize<MainClass>(json);
var json2 = serializer.Serialize(main1);
Debug.WriteLine(json2);
var main2 = serializer.Deserialize<MainClass>(json2);
Debug.Assert(main1.@class.ToStringWithReflection() == main2.@class.ToStringWithReflection()); // No assert
Debug.Assert(main1.classes.Select(c => c.ToStringWithReflection()).SequenceEqual(main2.classes.Select(c => c.ToStringWithReflection()))); // no assert
}
}
方案二:DataContractJsonSerializer
方案
WCF 及其 DataContractSerializer
s 仅 使用具体类型并且不序列化接口。因此,如果你想使用这些序列化器,你必须在内部使用具体的 classes 并将它们作为接口呈现给外部世界,例如:
public interface IMainInterface
{
IClass1 Value1 { get; set; }
IList<IClass2> Value2 { get; set; }
}
[Serializable]
[DataContract(Name = "MainClass")]
public class MainClass : IMainInterface
{
[DataMember(Name = "class")]
Class1 RealValue1 { get; set; }
[DataMember(Name = "classes")]
private List<Class2> RealList2 { get; set; }
IList<IClass2> list2Proxy; // can't be readonly because the DataContactJsonSerializer does not call the default constructor.
private IList<IClass2> List2Proxy
{
get
{
if (list2Proxy == null)
Interlocked.CompareExchange(ref list2Proxy, new ConvertingList<Class2, IClass2>(() => this.RealList2, c => c, ToClass), null);
return list2Proxy;
}
}
Class2 ToClass(IClass2 iClass)
{
// REWRITE TO FIT YOUR NEEDS
return (Class2)iClass;
}
Class1 ToClass(IClass1 iClass)
{
// REWRITE TO FIT YOUR NEEDS
return (Class1)iClass;
}
public MainClass()
{
RealList2 = new List<Class2>();
}
[IgnoreDataMember]
public IClass1 Value1
{
get
{
return RealValue1;
}
set
{
RealValue1 = ToClass(value);
}
}
[IgnoreDataMember]
public IList<IClass2> Value2
{
get
{
return List2Proxy;
}
set
{
if (value == null)
{
RealList2.Clear();
return;
}
if (List2Proxy == value)
return;
RealList2 = value.Select<IClass2, Class2>(ToClass).ToList();
}
}
}
public class ConvertingList<TIn, TOut> : IList<TOut>
{
readonly Func<IList<TIn>> getList;
readonly Func<TIn, TOut> toOuter;
readonly Func<TOut, TIn> toInner;
public ConvertingList(Func<IList<TIn>> getList, Func<TIn, TOut> toOuter, Func<TOut, TIn> toInner)
{
if (getList == null || toOuter == null || toInner == null)
throw new ArgumentNullException();
this.getList = getList;
this.toOuter = toOuter;
this.toInner = toInner;
}
IList<TIn> List { get { return getList(); } }
TIn ToInner(TOut outer) { return toInner(outer); }
TOut ToOuter(TIn inner) { return toOuter(inner); }
#region IList<TOut> Members
public int IndexOf(TOut item)
{
return List.IndexOf(toInner(item));
}
public void Insert(int index, TOut item)
{
List.Insert(index, ToInner(item));
}
public void RemoveAt(int index)
{
List.RemoveAt(index);
}
public TOut this[int index]
{
get
{
return ToOuter(List[index]);
}
set
{
List[index] = ToInner(value);
}
}
#endregion
#region ICollection<TOut> Members
public void Add(TOut item)
{
List.Add(ToInner(item));
}
public void Clear()
{
List.Clear();
}
public bool Contains(TOut item)
{
return List.Contains(ToInner(item));
}
public void CopyTo(TOut[] array, int arrayIndex)
{
foreach (var item in this)
array[arrayIndex++] = item;
}
public int Count
{
get { return List.Count; }
}
public bool IsReadOnly
{
get { return List.IsReadOnly; }
}
public bool Remove(TOut item)
{
return List.Remove(ToInner(item));
}
#endregion
#region IEnumerable<TOut> Members
public IEnumerator<TOut> GetEnumerator()
{
foreach (var item in List)
yield return ToOuter(item);
}
#endregion
#region IEnumerable Members
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
}
请注意,如果调用者尝试设置 IClass1
或 IClass2
而不是实际的 Class1
或 Class2
,则 InvalidCastException
将是抛出。因此,这给出了隐藏接口实现的外观,而没有真正保持实现私有。
我正在开发一个调用第 3 方 REST 服务的 c#.dotNet 项目。
样本Class结构:
[Serializable]
[DataContract(Name = "MainClass")]
[KnownType(typeof(Class1))]
[KnownType(typeof(Class2))]
public class MainClass : IMainInterface
{
public MainClass()
{
Value2 = new List<IClass2>();
}
[DataMember(Name = "class")]
public IClass1 Value1 { get; set; }
[DataMember(Name = "classes")]
public List<IClass2> Value2 { get; set; }
}
[Serializable]
[Export(typeof(IClass1))]
[ExportMetadata("IClass1", "Class1")]
[DataContract(Name = "class1")]
public class Class1 : IClass1
{
[ImportingConstructor]
public Class1()
{
}
[DataMember(Name = "prop1")]
public string Prop1 { get; set; }
[DataMember(Name = "prop2")]
public string Prop2 { get; set; }
[DataMember(Name = "prop3")]
public string Prop3 { get; set; }
}
[Serializable]
[Export(typeof(IClass2))]
[ExportMetadata("IClass2", "Class2")]
[DataContract]
public class Class2 : IClass2
{
[ImportingConstructor]
public Class2()
{ }
[DataMember(Name = "propA")]
public string PropA { get; set; }
[DataMember(Name = "propB")]
public string PropB { get; set; }
[DataMember(Name = "propC")]
public string PropC { get; set; }
}
public interface IMainInterface
{
IClass1 Value1 { get; set; }
List<IClass2> Value2 { get; set; }
}
public interface IClass1
{
string Prop1 { get; set; }
string Prop2 { get; set; }
string Prop3 { get; set; }
}
public interface IClass2
{
string PropA { get; set; }
string PropB { get; set; }
string PropC { get; set; }
}
Json_String1:(带有类型提示)
{
"class":
{"__type":"class1:#WpfApplication1","prop1":"TestVal0","prop2":"TestVal2","prop3":"TestVal3"},
"classes":
[
{"__type":"Class2:#WpfApplication1","propA":"A","propB":"B","propC":"C"},
{"__type":"Class2:#WpfApplication1","propA":"X","propB":"Y","propC":"Z"},
{"__type":"Class2:#WpfApplication1","propA":"1","propB":"2","propC":"3"}
]
}
Json_String2:(无类型提示)
{
"class":
{"prop1":"TestVal0","prop2":"TestVal2","prop3":"TestVal3"},
"classes":
[
{"propA":"A","propB":"B","propC":"C"},
{"propA":"X","propB":"Y","propC":"Z"},
{"propA":"1","propB":"2","propC":"3"}
]
}
因此,对于给定的 class 结构,如果我使用 DataContractJsonSerializer
生成 json(MainClass
的对象),我将得到 Json_String1 如果我直接反序列化,它工作正常。
而当 GET 获取数据时,响应是 Json_String2(w/o 类型提示)。因此,在反序列化时出现以下错误。
InvalidCastException was unhandled. Unable to cast object of type 'System.Object' to type 'WpfApplication1.IClass2'.
现在,我必须通过添加类型提示手动修改 json(字符串操作),才能成功反序列化它。
问题 1) 如何避免这种 Json 字符串操作反序列化?
问题 2) 如何在没有类型提示的情况下创建 json?
编辑: 1. 新增IMainInterface,由MainClass实现。 2. 点网框架 4
问题是因为您试图反序列化为只有接口的 class。当 JSON 指定 __type 时,它显然有效。但是当它不存在时(就像在您的第二个 JSON 示例中一样),ReadObject 无法自动将接口解析为其实现。
尝试使用 MainClass
中的具体 classes Class1
、Class2
而不是它们的接口(IClass1、IClass2)。其余代码可以保持原样。您的两个 Json 示例都可以正常工作。
[Serializable]
[DataContract(Name = "MainClass")]
[KnownType(typeof(Class1))]
[KnownType(typeof(Class2))]
public class MainClass
{
public MainClass()
{
Value2 = new List<Class2>();
}
[DataMember(Name = "class")]
public Class1 Value1 { get; set; }
[DataMember(Name = "classes")]
public List<Class2> Value2 { get; set; }
}
由于 none 的 classes 实际上是多态的,因此有几个解决方案可用,它们使用 .Net 内置 class 库:
方案一:JavaScriptSerializer
方案
JavaScriptSerializer
makes it easy to remap interfaces to classes during deserialization by using a JavaScriptConverter
。但是,它不允许重新映射字段和 属性 名称,因此您的 属性 名称 必须与 [=49] 中的名称相匹配=] 您希望处理 。以下转换器可以解决问题:
public class InterfaceToClassConverter<TInterface, TClass> : JavaScriptConverter where TClass : class, TInterface
{
public InterfaceToClassConverter()
{
if (typeof(TInterface) == typeof(TClass))
throw new ArgumentException(string.Format("{0} and {1} must not be the same type", typeof(TInterface).FullName, typeof(TClass).FullName)); // Or else you would get infinite recursion!
if (!typeof(TInterface).IsInterface)
throw new ArgumentException(string.Format("{0} must be an interface", typeof(TInterface).FullName));
if (typeof(TClass).IsInterface)
throw new ArgumentException(string.Format("{0} must be a class not an interface", typeof(TClass).FullName));
}
public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
{
if (type == typeof(TInterface))
return serializer.ConvertToType<TClass>(dictionary);
return null;
}
public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
{
throw new NotImplementedException();
}
public override IEnumerable<Type> SupportedTypes
{
get
{
// For an interface-valued property such as "IFoo Foo { getl set; },
// When serializing, JavaScriptSerializer knows the actual concrete type being serialized -- which is never an interface.
// When deserializing, JavaScriptSerializer only knows the expected type, which is an interface. Thus by returning
// only typeof(TInterface), we ensure this converter will only be called during deserialization, not serialization.
return new[] { typeof(TInterface) };
}
}
}
并像这样使用它:
public interface IMainInterface
{
IClass1 @class { get; set; } // NOTICE ALL PROPERTIES WERE RENAMED TO MATCH THE JSON NAMES.
List<IClass2> classes { get; set; }
}
[Serializable]
[DataContract(Name = "MainClass")]
[KnownType(typeof(Class1))]
[KnownType(typeof(Class2))]
public class MainClass : IMainInterface
{
public MainClass()
{
classes = new List<IClass2>();
}
[DataMember(Name = "class")]
public IClass1 @class { get; set; }
[DataMember(Name = "classes")]
public List<IClass2> classes { get; set; }
}
[Serializable]
[DataContract(Name = "class1")]
public class Class1 : IClass1
{
public Class1() {}
[DataMember(Name = "prop1")]
public string prop1 { get; set; }
[DataMember(Name = "prop2")]
public string prop2 { get; set; }
[DataMember(Name = "prop3")]
public string prop3 { get; set; }
}
[Serializable]
[DataContract]
public class Class2 : IClass2
{
public Class2() { }
[DataMember(Name = "propA")]
public string propA { get; set; }
[DataMember(Name = "propB")]
public string propB { get; set; }
[DataMember(Name = "propC")]
public string propC { get; set; }
}
public interface IClass1
{
string prop1 { get; set; }
string prop2 { get; set; }
string prop3 { get; set; }
}
public interface IClass2
{
string propA { get; set; }
string propB { get; set; }
string propC { get; set; }
}
public static class TestJavaScriptConverter
{
public static void Test()
{
string json = @"
{
""class"":
{""prop1"":""TestVal0"",""prop2"":""TestVal2"",""prop3"":""TestVal3""},
""classes"":
[
{""propA"":""A"",""propB"":""B"",""propC"":""C""},
{""propA"":""X"",""propB"":""Y"",""propC"":""Z""},
{""propA"":""1"",""propB"":""2"",""propC"":""3""}
]
}";
var serializer = new JavaScriptSerializer();
serializer.RegisterConverters(new JavaScriptConverter[] { new InterfaceToClassConverter<IClass1, Class1>(), new InterfaceToClassConverter<IClass2, Class2>() });
var main1 = serializer.Deserialize<MainClass>(json);
var json2 = serializer.Serialize(main1);
Debug.WriteLine(json2);
var main2 = serializer.Deserialize<MainClass>(json2);
Debug.Assert(main1.@class.ToStringWithReflection() == main2.@class.ToStringWithReflection()); // No assert
Debug.Assert(main1.classes.Select(c => c.ToStringWithReflection()).SequenceEqual(main2.classes.Select(c => c.ToStringWithReflection()))); // no assert
}
}
方案二:DataContractJsonSerializer
方案
WCF 及其 DataContractSerializer
s 仅 使用具体类型并且不序列化接口。因此,如果你想使用这些序列化器,你必须在内部使用具体的 classes 并将它们作为接口呈现给外部世界,例如:
public interface IMainInterface
{
IClass1 Value1 { get; set; }
IList<IClass2> Value2 { get; set; }
}
[Serializable]
[DataContract(Name = "MainClass")]
public class MainClass : IMainInterface
{
[DataMember(Name = "class")]
Class1 RealValue1 { get; set; }
[DataMember(Name = "classes")]
private List<Class2> RealList2 { get; set; }
IList<IClass2> list2Proxy; // can't be readonly because the DataContactJsonSerializer does not call the default constructor.
private IList<IClass2> List2Proxy
{
get
{
if (list2Proxy == null)
Interlocked.CompareExchange(ref list2Proxy, new ConvertingList<Class2, IClass2>(() => this.RealList2, c => c, ToClass), null);
return list2Proxy;
}
}
Class2 ToClass(IClass2 iClass)
{
// REWRITE TO FIT YOUR NEEDS
return (Class2)iClass;
}
Class1 ToClass(IClass1 iClass)
{
// REWRITE TO FIT YOUR NEEDS
return (Class1)iClass;
}
public MainClass()
{
RealList2 = new List<Class2>();
}
[IgnoreDataMember]
public IClass1 Value1
{
get
{
return RealValue1;
}
set
{
RealValue1 = ToClass(value);
}
}
[IgnoreDataMember]
public IList<IClass2> Value2
{
get
{
return List2Proxy;
}
set
{
if (value == null)
{
RealList2.Clear();
return;
}
if (List2Proxy == value)
return;
RealList2 = value.Select<IClass2, Class2>(ToClass).ToList();
}
}
}
public class ConvertingList<TIn, TOut> : IList<TOut>
{
readonly Func<IList<TIn>> getList;
readonly Func<TIn, TOut> toOuter;
readonly Func<TOut, TIn> toInner;
public ConvertingList(Func<IList<TIn>> getList, Func<TIn, TOut> toOuter, Func<TOut, TIn> toInner)
{
if (getList == null || toOuter == null || toInner == null)
throw new ArgumentNullException();
this.getList = getList;
this.toOuter = toOuter;
this.toInner = toInner;
}
IList<TIn> List { get { return getList(); } }
TIn ToInner(TOut outer) { return toInner(outer); }
TOut ToOuter(TIn inner) { return toOuter(inner); }
#region IList<TOut> Members
public int IndexOf(TOut item)
{
return List.IndexOf(toInner(item));
}
public void Insert(int index, TOut item)
{
List.Insert(index, ToInner(item));
}
public void RemoveAt(int index)
{
List.RemoveAt(index);
}
public TOut this[int index]
{
get
{
return ToOuter(List[index]);
}
set
{
List[index] = ToInner(value);
}
}
#endregion
#region ICollection<TOut> Members
public void Add(TOut item)
{
List.Add(ToInner(item));
}
public void Clear()
{
List.Clear();
}
public bool Contains(TOut item)
{
return List.Contains(ToInner(item));
}
public void CopyTo(TOut[] array, int arrayIndex)
{
foreach (var item in this)
array[arrayIndex++] = item;
}
public int Count
{
get { return List.Count; }
}
public bool IsReadOnly
{
get { return List.IsReadOnly; }
}
public bool Remove(TOut item)
{
return List.Remove(ToInner(item));
}
#endregion
#region IEnumerable<TOut> Members
public IEnumerator<TOut> GetEnumerator()
{
foreach (var item in List)
yield return ToOuter(item);
}
#endregion
#region IEnumerable Members
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
}
请注意,如果调用者尝试设置 IClass1
或 IClass2
而不是实际的 Class1
或 Class2
,则 InvalidCastException
将是抛出。因此,这给出了隐藏接口实现的外观,而没有真正保持实现私有。