C#的yield return返回不可序列化的IEnumerable和IEnumerator
.NET中的大多数常见IEnumerable和IEnumerator都是可以序列化的(有Serializable特性)。比如Array,Dictionary<K,V>, HashSet<T>, List<T>, LinkedList, Queue<T>, Stack<T>, String, ResourceSet……等等类。而且一般可序列化的IEnumerable的GetEnumerator方法会返回一个同样可序列化的迭代器(IEnumerator)。
注意其中已0开始的一维数组会返回Array.SZArrayEnumerator迭代器(其IEnumerable<T>的GetEnumerator显示接口执行还会返回System.SZArrayHelper.SZGenericArrayEnumerator<T>迭代器)。而其他类型的数组会返回Array.ArrayEnumerator迭代器。三者也都是可序列化的
但是yield return返回产生的IEnumerable和IEnumerator是不可序列化的,这一点很重要!尤其在扩应用程序域(AppDomain)程序数据交互的情景,此时要求数据要么可序列化要么继承MarshalByRefObject,也就是所谓的按值封送和按引用封送。
通过一个简单的程序就可以验证:
static void Main()
{
Console.WriteLine(able1().GetType());
Console.WriteLine(able2().GetType());
Console.WriteLine(tor1().GetType());
Console.WriteLine(tor2().GetType());
}
static IEnumerable<int> able1()
{
for (int i = 0; i < 5; i++)
{
yield return i + 1;
}
}
static IEnumerable<int> able2()
{
return new int[] { 1, 2, 3, 4, 5 };
}
static IEnumerator<int> tor1()
{
for (int i = 0; i < 5; i++)
{
yield return i + 1;
}
}
static IEnumerator<int> tor2()
{
return ((IEnumerable<int>)new int[] { 1, 2, 3, 4, 5 }).GetEnumerator();
}
输出:
Mgen.Program+<able1>d__0
System.Int32[]
Mgen.Program+<tor1>d__4
System.SZArrayHelper+SZGenericArrayEnumerator`1[System.Int32]
第二个和第四个类都是可序列化的,而用yield return返回的对象都是编译器自动生成的,且都不是可序列化的,下面是Reflector下的两个类定义:
[CompilerGenerated]
private sealed class <able1>d__0 : IEnumerable<int>, IEnumerable, IEnumerator<int>, IEnumerator, IDisposable
[CompilerGenerated]
private sealed class <tor1>d__4 : IEnumerator<int>, IEnumerator, IDisposable
上面提到过这样的IEnumerable和IEnumerator无法在跨应用程序域中进行封送。写这篇文章也是由于今天遇到这种问题:
class Program: MarshalByRefObject
{
static void Main()
{
//创建应用程序域
AppDomain appDomain = AppDomain.CreateDomain("new appdomain");
//在另一个应用程序域中创建Program对象
Program pro = (Program)appDomain.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName,
typeof(Program).FullName);
//此处异常:返回值必须可以被序列化!
IEnumerator<int> iter = pro.test();
}
IEnumerator<int> test()
{
for (int i = 0; i < 5; i++)
{
yield return i + 1;
}
}
}
解决方案当然就是不用yield return或者自定义自己的可序列化的迭代器。
当然yield return在某些时候还是很有必要的,当然把编译器yield return产生的非可序列化对象做一个简单的包装也可以在跨应用程序域中使用。
下面是完整的代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Collections;
using System.Runtime.Remoting;
using System.Reflection;
namespace Mgen.TTC
{
class Program : MarshalByRefObject
{
static void Main()
{
//创建应用程序域
AppDomain appDomain = AppDomain.CreateDomain("new appdomain");
//在另一个应用程序域中创建Program对象
Program pro = (Program)appDomain.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName,
typeof(Program).FullName);
IEnumerator<int> iter = pro.test();
while (iter.MoveNext())
Console.WriteLine(iter.Current);
Console.WriteLine("是否是透明代理: {0}", RemotingServices.IsTransparentProxy(iter));
}
IEnumerator<int> internal_test()
{
for (int i = 0; i < 5; i++)
{
yield return i + 1;
}
}
IEnumerator<int> test()
{
return new MyEnumerator<int>(internal_test());
}
}
class MyEnumerator<T> : MarshalByRefObject, IEnumerator<T>
{
IEnumerator<T> iter;
public MyEnumerator(IEnumerator<T> i)
{
iter = i;
}
public T Current
{
get { return iter.Current; }
}
public void Dispose()
{
iter.Dispose();
}
object IEnumerator.Current
{
get { return ((IEnumerator)iter).Current; }
}
public bool MoveNext()
{
return iter.MoveNext();
}
public void Reset()
{
iter.Reset();
}
}
}
输出:
1
2
3
4
5
是否是透明代理: True
OK,迭代器可以被使用且属于透明代理(在另一个应用程序域)。