2010-07-20 16 views
16

Có thể tuần tự hóa một phương thức có chứa câu lệnh yield (hoặc một lớp chứa phương thức như vậy) sao cho khi bạn bù nước lại lớp, trạng thái bên trong của trình lặp được tạo không?Tuần tự hóa và câu lệnh Yield

+1

Bạn muốn bắt đầu chạy phương pháp này ở một nơi, và tiếp tục chạy nó ở một nơi khác nhau? – arootbeer

+1

@arootbeer: Nó có thể là một "lưu tiến bộ" điều quá, nó không phải được gửi qua dây nhất thiết. –

+0

@Scott Stafford: Điều đó có ý nghĩa hơn. – arootbeer

Trả lời

8

Có, bạn có thể thực hiện việc này. Với sự cẩn trọng.

Một ví dụ về serializing một phương thức với một yield, deserializing và tiếp tục có thể được tìm thấy ở đây: http://www.agilekiwi.com/dotnet/CountingDemo.cs (Web Archive Link).

Nói chung, cố gắng tuần tự hóa mà không thực hiện thêm một số công việc sẽ thất bại. Đây là bcause các trình biên dịch tạo ra các lớp học không được đánh dấu bằng thuộc tính Serializable. Tuy nhiên, bạn có thể giải quyết vấn đề này. Tôi sẽ lưu ý lý do chúng không được đánh dấu bằng serializable là vì chúng là chi tiết triển khai và có thể phá vỡ các thay đổi trong các phiên bản sau, vì vậy bạn không thể deserialize nó trong một phiên bản mới hơn.

Liên quan đến câu hỏi tôi đã hỏi trên how to serialize anonymous delegates, cũng sẽ hoạt động đối với trường hợp này.

Đây là mã nguồn của "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 
{ 
    /// <summary> 
    /// This is the class we will enumerate over 
    /// </summary> 
    [Serializable] 
    public class SimpleEnumerable 
    { 
     public IEnumerator<string> 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<string> AdvancedFoo() 
     { 
      yield return "One"; 
      foreach (string s in Letters()) 
       yield return "Two " + s; 
      yield return "Three"; 
     } 

     private IEnumerable<string> Letters() 
     { 
      yield return "a"; 
      yield return "b"; 
      yield return "c"; 
     } 
     #endregion 
    } 

    /// <summary> 
    /// This is the command-line program which calls the iterator and serializes the state 
    /// </summary> 
    public class Program 
    { 
     public static void Main() 
     { 
      // Create/restore the iterator 
      IEnumerator<string> 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<string> LoadIterator() 
     { 
      using (FileStream stream = new FileStream(StateFile, FileMode.Open)) 
      { 
       ISurrogateSelector selector = new EnumerationSurrogateSelector(); 
       IFormatter f = new SoapFormatter(selector, new StreamingContext()); 
       return (IEnumerator<string>)f.Deserialize(stream); 
      } 
     } 

     static void SaveIterator(IEnumerator<string> 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 
} 
+0

+1 rất thú vị! – Abel

+0

thật không may, liên kết đã bị hỏng –

+0

@modosansreves may mắn thay nó đã được sao lưu trên lưu trữ web. –

5

Nội bộ, câu lệnh yield được chuyển thành máy trạng thái được triển khai làm lớp thực hiện giao diện IEnumerator. Nó cho phép lặp đi lặp lại kết quả bằng cách sử dụng nhiều câu lệnh foreach cùng một lúc. Lớp đó không hiển thị với mã của bạn, nó không được đánh dấu là có thể tuần tự hóa được.

Vì vậy, câu trả lời là không, điều đó là không thể. Tuy nhiên, bạn có thể thực hiện điều tra viên mong muốn của chính nó, nhưng nó đòi hỏi nhiều lao động hơn yield.

+4

Tôi có thể nói điều này không hoàn toàn chính xác. Có thể, nhưng có lẽ không khuyến khích mạnh mẽ. –

1

Chỉ cần đảm bảo rằng trước khi bạn gọi sản lượng, bạn lưu trạng thái (tức là vị trí của trình vòng lặp) trong trường có thể tuần tự hóa (trường vị trí hoặc bất kỳ thứ gì bạn gọi). Sau đó, khi lớp học được deserialized, chỉ cần tiếp tục nơi bạn rời đi, sử dụng trường vị trí.

Nhưng, điều này sẽ hữu ích khi nào? Bạn có kế hoạch tuần tự hóa các đối tượng ở giữa vòng lặp foreach không? Có thể bạn sẽ dễ dàng hơn nhiều nếu bạn cung cấp cho bạn phương thức SetIteratorPosition(), mặc định là vị trí hiện tại. Nó rõ ràng hơn việc thêm các hiệu ứng phụ vào hành vi được xác định rõ ràng (năng suất) và mọi người sẽ hiểu rằng IteratorPosition có thể được lưu lại.

Lưu ý: không thể tuần tự hóa các phương thức. Bạn tuần tự hóa dữ liệu, tức là thuộc tính và trường.

1

Có. Bất kỳ phương thức nào trả về một IEnumerable đều có thể có mã riêng của nó cho yield return bất kể bạn nói gì. Nếu bạn tuần tự hóa trạng thái bên trong của đối tượng của bạn như những gì nó đã được lặp đi lặp lại và làm thế nào đến nay nó có, sau đó bạn có thể tải lại trạng thái đó tại một số thời gian trong tương lai, và tiếp tục liệt kê quyền nơi bạn rời đi.