序列化和Yield语句

是否可以序列化包含yield语句的方法(或包含此类方法的类),以便在对类进行重新水化时,保留生成的迭代器的内部状态?

是的,你可以这样做。 有警告。

可以在此处找到使用yield ,反序列化和继续序列化方法的示例: http : //www.agilekiwi.com/dotnet/CountingDemo.cs ( Web Archive Link )。

通常,尝试序列化而不做一些额外的工作将失败。 这是因为编译器生成的类没有标记Serializable属性。 但是,你可以解决这个问题。

我会注意到它们没有标记为可序列化的原因是因为它们是一个实现细节,并且在将来的版本中会受到重大更改,因此您可能无法在较新版本中反序列化它。

与我询问如何序列化匿名委托的问题相关,这也适用于这种情况。

这是“hack”的源代码:

 // Copyright © 2007 John M Rusk (http://www.agilekiwi.com) // // You may use this source code in any manner you wish, subject to // the following conditions: // // (a) The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // (b) THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Soap; namespace AgileKiwi.PersistentIterator.Demo { ///  /// This is the class we will enumerate over ///  [Serializable] public class SimpleEnumerable { public IEnumerator Foo() { yield return "One"; yield return "Two"; yield return "Three"; } #region Here is a more advanced example // This shows that the solution even works for iterators which call other iterators // See SimpleFoo below for a simpler example public IEnumerator AdvancedFoo() { yield return "One"; foreach (string s in Letters()) yield return "Two " + s; yield return "Three"; } private IEnumerable Letters() { yield return "a"; yield return "b"; yield return "c"; } #endregion } ///  /// This is the command-line program which calls the iterator and serializes the state ///  public class Program { public static void Main() { // Create/restore the iterator IEnumerator e; if (File.Exists(StateFile)) e = LoadIterator(); else e = (new SimpleEnumerable()).Foo(); // start new iterator // Move to next item and display it. // We can't use foreach here, because we only want to get ONE // result at a time. if (e.MoveNext()) Console.WriteLine(e.Current); else Console.WriteLine("Finished. Delete the state.xml file to restart"); // Save the iterator state back to the file SaveIterator(e); // Pause if running from the IDE if (Debugger.IsAttached) { Console.Write("Press any key..."); Console.ReadKey(); } } static string StateFile { get { return Path.Combine( Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "State.xml"); } } static IEnumerator LoadIterator() { using (FileStream stream = new FileStream(StateFile, FileMode.Open)) { ISurrogateSelector selector = new EnumerationSurrogateSelector(); IFormatter f = new SoapFormatter(selector, new StreamingContext()); return (IEnumerator)f.Deserialize(stream); } } static void SaveIterator(IEnumerator e) { using (FileStream stream = new FileStream(StateFile, FileMode.Create)) { ISurrogateSelector selector = new EnumerationSurrogateSelector(); IFormatter f = new SoapFormatter(selector, new StreamingContext()); f.Serialize(stream, e); } #region Note: The above code puts the name of the compiler-generated enumerator class... // into the serialized output. Under what circumstances, if any, might a recompile result in // a different class name? I have not yet investigated what the answer might be. // I suspect MS provide no guarantees in that regard. #endregion } } #region Helper classes to serialize iterator state // See http://msdn.microsoft.com/msdnmag/issues/02/09/net/#S3 class EnumerationSurrogateSelector : ISurrogateSelector { ISurrogateSelector _next; public void ChainSelector(ISurrogateSelector selector) { _next = selector; } public ISurrogateSelector GetNextSelector() { return _next; } public ISerializationSurrogate GetSurrogate(Type type, StreamingContext context, out ISurrogateSelector selector) { if (typeof(System.Collections.IEnumerator).IsAssignableFrom(type)) { selector = this; return new EnumeratorSerializationSurrogate(); } else { //todo: check this section if (_next == null) { selector = null; return null; } else { return _next.GetSurrogate(type, context, out selector); } } } } // see http://msdn.microsoft.com/msdnmag/issues/02/09/net/#S3 class EnumeratorSerializationSurrogate : ISerializationSurrogate { public void GetObjectData(object obj, SerializationInfo info, StreamingContext context) { foreach(FieldInfo f in obj.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) info.AddValue(f.Name, f.GetValue(obj)); } public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector) { foreach (FieldInfo f in obj.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) f.SetValue(obj, info.GetValue(f.Name, f.FieldType)); return obj; } } #endregion } 

在内部, yield语句被转换为实现为实现IEnumerator接口的类的状态机。 它允许同时使用多个foreach语句迭代结果集。 该类对您的代码不可见,它未标记为可序列化。

所以,答案是否定的,这是不可能的。 但是,您可以单独实现所需的枚举器,但它需要更多的劳动力而不是yield

只需确保在调用yield之前,将状态(即迭代器位置)保存在可序列化字段(位置字段或其他任何调用它)中。 然后,当反序列化类时,只需使用位置字段继续上次停止的位置。

但是,这什么时候会有用? 您是否计划在foreach循环中序列化对象? 如果你给你一个SetIteratorPosition()方法,默​​认为当前位置,也许你会让它变得容易多了。 它比为现有明确定义的行为(产量)添加副作用更清晰,每个人都会理解可以保存IteratorPosition

注意:方法无法序列化。 您序列化数据,即属性和字段。

是。 任何返回IEnumerable的方法都可以拥有它自己的yield return代码,无论你告诉它什么。 如果你序列化对象的内部状态,看它是迭代的内容以及它有多远,那么你可以在将来的某个时间重新加载该状态,并在你离开的地方继续枚举。