วิธีที่ถูกต้องในการสร้าง. NET Exception แบบอนุกรมคืออะไร?


225

โดยเฉพาะอย่างยิ่งเมื่อข้อยกเว้นมีวัตถุที่กำหนดเองซึ่งอาจหรือไม่สามารถทำให้เป็นอนุกรมได้

นำตัวอย่างนี้:

public class MyException : Exception
{
    private readonly string resourceName;
    private readonly IList<string> validationErrors;

    public MyException(string resourceName, IList<string> validationErrors)
    {
        this.resourceName = resourceName;
        this.validationErrors = validationErrors;
    }

    public string ResourceName
    {
        get { return this.resourceName; }
    }

    public IList<string> ValidationErrors
    {
        get { return this.validationErrors; }
    }
}

หากข้อยกเว้นนี้เป็นอนุกรมและไม่ต่อเนื่องคุณสมบัติที่กำหนดเองสองรายการ ( ResourceNameและValidationErrors) จะไม่ถูกเก็บไว้ nullคุณสมบัติจะกลับ

มีรูปแบบรหัสทั่วไปสำหรับการใช้การทำให้เป็นอนุกรมสำหรับข้อยกเว้นที่กำหนดเองหรือไม่

คำตอบ:


411

การใช้งานฐานโดยไม่มีคุณสมบัติที่กำหนดเอง

SerializableExceptionWithoutCustomProperties.cs:

namespace SerializableExceptions
{
    using System;
    using System.Runtime.Serialization;

    [Serializable]
    // Important: This attribute is NOT inherited from Exception, and MUST be specified 
    // otherwise serialization will fail with a SerializationException stating that
    // "Type X in Assembly Y is not marked as serializable."
    public class SerializableExceptionWithoutCustomProperties : Exception
    {
        public SerializableExceptionWithoutCustomProperties()
        {
        }

        public SerializableExceptionWithoutCustomProperties(string message) 
            : base(message)
        {
        }

        public SerializableExceptionWithoutCustomProperties(string message, Exception innerException) 
            : base(message, innerException)
        {
        }

        // Without this constructor, deserialization will fail
        protected SerializableExceptionWithoutCustomProperties(SerializationInfo info, StreamingContext context) 
            : base(info, context)
        {
        }
    }
}

การใช้งานเต็มรูปแบบด้วยคุณสมบัติที่กำหนดเอง

การใช้งานอย่างสมบูรณ์ของข้อยกเว้นแบบอนุกรมที่กำหนดเอง ( MySerializableException) และsealedข้อยกเว้นที่ได้รับ( MyDerivedSerializableException)

ประเด็นหลักเกี่ยวกับการดำเนินการนี้สรุปได้ที่นี่:

  1. คุณต้องตกแต่งแต่ละคลาสที่ได้รับมาพร้อมกับ[Serializable]แอ็ตทริบิวต์ - แอ็ตทริบิวต์นี้ไม่ได้รับมาจากคลาสพื้นฐานและหากไม่ได้ระบุการทำให้เป็นอันดับจะล้มเหลวด้วยการSerializationExceptionระบุว่า"Type X ในแอสเซมบลี Y
  2. คุณจะต้องดำเนินการเป็นอันดับที่กำหนดเอง [Serializable]แอตทริบิวต์อย่างเดียวไม่พอ - ExceptionการดำเนินการISerializableซึ่งหมายความว่าการเรียนของคุณได้นอกจากนี้ยังจะต้องดำเนินการเป็นอันดับที่กำหนดเอง สิ่งนี้เกี่ยวข้องกับสองขั้นตอน:
    1. ให้คอนสตรัคเป็นอันดับ ตัวสร้างนี้ควรจะเป็นprivateถ้าคลาสของคุณเป็นsealedอย่างอื่นมันควรจะprotectedอนุญาตให้เข้าถึงคลาสที่ได้รับ
    2. แทนที่ GetObjectData ()และตรวจสอบให้แน่ใจว่าคุณโทรผ่านไปbase.GetObjectData(info, context)ยังจุดสิ้นสุดเพื่อให้คลาสฐานบันทึกสถานะของตนเอง

SerializableExceptionWithCustomProperties.cs:

namespace SerializableExceptions
{
    using System;
    using System.Collections.Generic;
    using System.Runtime.Serialization;
    using System.Security.Permissions;

    [Serializable]
    // Important: This attribute is NOT inherited from Exception, and MUST be specified 
    // otherwise serialization will fail with a SerializationException stating that
    // "Type X in Assembly Y is not marked as serializable."
    public class SerializableExceptionWithCustomProperties : Exception
    {
        private readonly string resourceName;
        private readonly IList<string> validationErrors;

        public SerializableExceptionWithCustomProperties()
        {
        }

        public SerializableExceptionWithCustomProperties(string message) 
            : base(message)
        {
        }

        public SerializableExceptionWithCustomProperties(string message, Exception innerException)
            : base(message, innerException)
        {
        }

        public SerializableExceptionWithCustomProperties(string message, string resourceName, IList<string> validationErrors)
            : base(message)
        {
            this.resourceName = resourceName;
            this.validationErrors = validationErrors;
        }

        public SerializableExceptionWithCustomProperties(string message, string resourceName, IList<string> validationErrors, Exception innerException)
            : base(message, innerException)
        {
            this.resourceName = resourceName;
            this.validationErrors = validationErrors;
        }

        [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]
        // Constructor should be protected for unsealed classes, private for sealed classes.
        // (The Serializer invokes this constructor through reflection, so it can be private)
        protected SerializableExceptionWithCustomProperties(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
            this.resourceName = info.GetString("ResourceName");
            this.validationErrors = (IList<string>)info.GetValue("ValidationErrors", typeof(IList<string>));
        }

        public string ResourceName
        {
            get { return this.resourceName; }
        }

        public IList<string> ValidationErrors
        {
            get { return this.validationErrors; }
        }

        [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]
        public override void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            if (info == null)
            {
                throw new ArgumentNullException("info");
            }

            info.AddValue("ResourceName", this.ResourceName);

            // Note: if "List<T>" isn't serializable you may need to work out another
            //       method of adding your list, this is just for show...
            info.AddValue("ValidationErrors", this.ValidationErrors, typeof(IList<string>));

            // MUST call through to the base class to let it save its own state
            base.GetObjectData(info, context);
        }
    }
}

DerivedSerializableExceptionWithAdditionalCustomProperties.cs:

namespace SerializableExceptions
{
    using System;
    using System.Collections.Generic;
    using System.Runtime.Serialization;
    using System.Security.Permissions;

    [Serializable]
    public sealed class DerivedSerializableExceptionWithAdditionalCustomProperty : SerializableExceptionWithCustomProperties
    {
        private readonly string username;

        public DerivedSerializableExceptionWithAdditionalCustomProperty()
        {
        }

        public DerivedSerializableExceptionWithAdditionalCustomProperty(string message)
            : base(message)
        {
        }

        public DerivedSerializableExceptionWithAdditionalCustomProperty(string message, Exception innerException) 
            : base(message, innerException)
        {
        }

        public DerivedSerializableExceptionWithAdditionalCustomProperty(string message, string username, string resourceName, IList<string> validationErrors) 
            : base(message, resourceName, validationErrors)
        {
            this.username = username;
        }

        public DerivedSerializableExceptionWithAdditionalCustomProperty(string message, string username, string resourceName, IList<string> validationErrors, Exception innerException) 
            : base(message, resourceName, validationErrors, innerException)
        {
            this.username = username;
        }

        [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]
        // Serialization constructor is private, as this class is sealed
        private DerivedSerializableExceptionWithAdditionalCustomProperty(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
            this.username = info.GetString("Username");
        }

        public string Username
        {
            get { return this.username; }
        }

        public override void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            if (info == null)
            {
                throw new ArgumentNullException("info");
            }
            info.AddValue("Username", this.username);
            base.GetObjectData(info, context);
        }
    }
}

ทดสอบหน่วย

MSTest หน่วยทดสอบสำหรับสามประเภทยกเว้นที่กำหนดไว้ข้างต้น

UnitTests.cs:

namespace SerializableExceptions
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Runtime.Serialization.Formatters.Binary;
    using Microsoft.VisualStudio.TestTools.UnitTesting;

    [TestClass]
    public class UnitTests
    {
        private const string Message = "The widget has unavoidably blooped out.";
        private const string ResourceName = "Resource-A";
        private const string ValidationError1 = "You forgot to set the whizz bang flag.";
        private const string ValidationError2 = "Wally cannot operate in zero gravity.";
        private readonly List<string> validationErrors = new List<string>();
        private const string Username = "Barry";

        public UnitTests()
        {
            validationErrors.Add(ValidationError1);
            validationErrors.Add(ValidationError2);
        }

        [TestMethod]
        public void TestSerializableExceptionWithoutCustomProperties()
        {
            Exception ex =
                new SerializableExceptionWithoutCustomProperties(
                    "Message", new Exception("Inner exception."));

            // Save the full ToString() value, including the exception message and stack trace.
            string exceptionToString = ex.ToString();

            // Round-trip the exception: Serialize and de-serialize with a BinaryFormatter
            BinaryFormatter bf = new BinaryFormatter();
            using (MemoryStream ms = new MemoryStream())
            {
                // "Save" object state
                bf.Serialize(ms, ex);

                // Re-use the same stream for de-serialization
                ms.Seek(0, 0);

                // Replace the original exception with de-serialized one
                ex = (SerializableExceptionWithoutCustomProperties)bf.Deserialize(ms);
            }

            // Double-check that the exception message and stack trace (owned by the base Exception) are preserved
            Assert.AreEqual(exceptionToString, ex.ToString(), "ex.ToString()");
        }

        [TestMethod]
        public void TestSerializableExceptionWithCustomProperties()
        {
            SerializableExceptionWithCustomProperties ex = 
                new SerializableExceptionWithCustomProperties(Message, ResourceName, validationErrors);

            // Sanity check: Make sure custom properties are set before serialization
            Assert.AreEqual(Message, ex.Message, "Message");
            Assert.AreEqual(ResourceName, ex.ResourceName, "ex.ResourceName");
            Assert.AreEqual(2, ex.ValidationErrors.Count, "ex.ValidationErrors.Count");
            Assert.AreEqual(ValidationError1, ex.ValidationErrors[0], "ex.ValidationErrors[0]");
            Assert.AreEqual(ValidationError2, ex.ValidationErrors[1], "ex.ValidationErrors[1]");

            // Save the full ToString() value, including the exception message and stack trace.
            string exceptionToString = ex.ToString();

            // Round-trip the exception: Serialize and de-serialize with a BinaryFormatter
            BinaryFormatter bf = new BinaryFormatter();
            using (MemoryStream ms = new MemoryStream())
            {
                // "Save" object state
                bf.Serialize(ms, ex);

                // Re-use the same stream for de-serialization
                ms.Seek(0, 0);

                // Replace the original exception with de-serialized one
                ex = (SerializableExceptionWithCustomProperties)bf.Deserialize(ms);
            }

            // Make sure custom properties are preserved after serialization
            Assert.AreEqual(Message, ex.Message, "Message");
            Assert.AreEqual(ResourceName, ex.ResourceName, "ex.ResourceName");
            Assert.AreEqual(2, ex.ValidationErrors.Count, "ex.ValidationErrors.Count");
            Assert.AreEqual(ValidationError1, ex.ValidationErrors[0], "ex.ValidationErrors[0]");
            Assert.AreEqual(ValidationError2, ex.ValidationErrors[1], "ex.ValidationErrors[1]");

            // Double-check that the exception message and stack trace (owned by the base Exception) are preserved
            Assert.AreEqual(exceptionToString, ex.ToString(), "ex.ToString()");
        }

        [TestMethod]
        public void TestDerivedSerializableExceptionWithAdditionalCustomProperty()
        {
            DerivedSerializableExceptionWithAdditionalCustomProperty ex = 
                new DerivedSerializableExceptionWithAdditionalCustomProperty(Message, Username, ResourceName, validationErrors);

            // Sanity check: Make sure custom properties are set before serialization
            Assert.AreEqual(Message, ex.Message, "Message");
            Assert.AreEqual(ResourceName, ex.ResourceName, "ex.ResourceName");
            Assert.AreEqual(2, ex.ValidationErrors.Count, "ex.ValidationErrors.Count");
            Assert.AreEqual(ValidationError1, ex.ValidationErrors[0], "ex.ValidationErrors[0]");
            Assert.AreEqual(ValidationError2, ex.ValidationErrors[1], "ex.ValidationErrors[1]");
            Assert.AreEqual(Username, ex.Username);

            // Save the full ToString() value, including the exception message and stack trace.
            string exceptionToString = ex.ToString();

            // Round-trip the exception: Serialize and de-serialize with a BinaryFormatter
            BinaryFormatter bf = new BinaryFormatter();
            using (MemoryStream ms = new MemoryStream())
            {
                // "Save" object state
                bf.Serialize(ms, ex);

                // Re-use the same stream for de-serialization
                ms.Seek(0, 0);

                // Replace the original exception with de-serialized one
                ex = (DerivedSerializableExceptionWithAdditionalCustomProperty)bf.Deserialize(ms);
            }

            // Make sure custom properties are preserved after serialization
            Assert.AreEqual(Message, ex.Message, "Message");
            Assert.AreEqual(ResourceName, ex.ResourceName, "ex.ResourceName");
            Assert.AreEqual(2, ex.ValidationErrors.Count, "ex.ValidationErrors.Count");
            Assert.AreEqual(ValidationError1, ex.ValidationErrors[0], "ex.ValidationErrors[0]");
            Assert.AreEqual(ValidationError2, ex.ValidationErrors[1], "ex.ValidationErrors[1]");
            Assert.AreEqual(Username, ex.Username);

            // Double-check that the exception message and stack trace (owned by the base Exception) are preserved
            Assert.AreEqual(exceptionToString, ex.ToString(), "ex.ToString()");
        }
    }
}

3
+1: แต่ถ้าคุณจะประสบปัญหามากนี้ฉันจะไปไกลและทำตามแนวทาง MS ทั้งหมดสำหรับการใช้ข้อยกเว้น สิ่งหนึ่งที่ฉันสามารถจำได้คือการจัดทำข้อ จำกัด มาตรฐาน MyException (), MyException (ข้อความสตริง) และ MyException (ข้อความสตริง, ข้อยกเว้น InnerException)
Joe

3
นอกจากนี้ - Framework Design Guideliness บอกว่าชื่อของข้อยกเว้นควรลงท้ายด้วย "Exception" สิ่งที่ต้องการ MyExceptionAndHereIsaQualifyingAdverbialPhrase ถูกยกเลิกการอ้างอิง msdn.microsoft.com/en-us/library/ms229064.aspx มี คนเคยกล่าวไว้ว่ารหัสที่เราให้ไว้ที่นี่มักจะถูกใช้เป็นรูปแบบดังนั้นเราจึงควรระมัดระวังในการทำให้ถูกต้อง
Cheeso

1
Cheeso: หนังสือ "Framework Design Guidelines" ในส่วนของการออกแบบข้อยกเว้นที่กำหนดเองกล่าวว่า: "อย่าให้ Constructor เหล่านี้ร่วมกัน (อย่างน้อย) ในทุกข้อยกเว้น" ดูที่นี่: blogs.msdn.com/kcwalina/archive/2006/07/05/657268.aspx เฉพาะคอนสตรัคเตอร์ (ข้อมูล SerializationInfo, บริบท StreamingContext) เป็นสิ่งจำเป็นสำหรับความถูกต้องของซีเรียลไลเซชันส่วนที่เหลือนั้นเป็นจุดเริ่มต้นที่ดีสำหรับ ตัดและวาง. เมื่อคุณตัดและวางอย่างไรก็ตามคุณจะเปลี่ยนชื่อชั้นเรียนอย่างแน่นอนดังนั้นฉันไม่คิดว่าการละเมิดข้อตกลงการตั้งชื่อยกเว้นมีความสำคัญที่นี่ ...
แดเนียล Fortunov

3
นี่เป็นคำตอบที่ยอมรับได้จริงสำหรับ. NET Core เช่นกัน? ใน. net core GetObjectDataไม่เคยถูกเรียกใช้ .. ไม่ว่าฉันจะสามารถแทนที่ToString()สิ่งใดที่จะเรียกใช้ได้
LP13

3
ดูเหมือนว่านี่ไม่ใช่วิธีที่พวกเขาทำในโลกใหม่ ตัวอย่างเช่นไม่มีการยกเว้นใน ASP.NET Core อย่างแท้จริง พวกเขาทั้งหมดละเว้นสิ่งที่ทำให้เป็นอันดับ: github.com/aspnet/Mvc/blob/…
bitbonk

25

ข้อยกเว้นนั้นเป็นอนุกรมแล้ว แต่คุณต้องแทนที่GetObjectDataวิธีการจัดเก็บตัวแปรของคุณและให้ตัวสร้างซึ่งสามารถเรียกได้เมื่อ re-hydrating วัตถุของคุณ

ดังนั้นตัวอย่างของคุณจะกลายเป็น:

[Serializable]
public class MyException : Exception
{
    private readonly string resourceName;
    private readonly IList<string> validationErrors;

    public MyException(string resourceName, IList<string> validationErrors)
    {
        this.resourceName = resourceName;
        this.validationErrors = validationErrors;
    }

    public string ResourceName
    {
        get { return this.resourceName; }
    }

    public IList<string> ValidationErrors
    {
        get { return this.validationErrors; }
    }

    [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter=true)]
    protected MyException(SerializationInfo info, StreamingContext context) : base (info, context)
    {
        this.resourceName = info.GetString("MyException.ResourceName");
        this.validationErrors = info.GetValue("MyException.ValidationErrors", typeof(IList<string>));
    }

    [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter=true)]
    public override void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        base.GetObjectData(info, context);

        info.AddValue("MyException.ResourceName", this.ResourceName);

        // Note: if "List<T>" isn't serializable you may need to work out another
        //       method of adding your list, this is just for show...
        info.AddValue("MyException.ValidationErrors", this.ValidationErrors, typeof(IList<string>));
    }

}

1
บ่อยครั้งที่คุณสามารถหนีไปได้ด้วยการเพิ่ม [Serializable] ให้กับชั้นเรียนของคุณ
Hallgrim

3
Hallgrim: การเพิ่ม [Serializable] ไม่เพียงพอหากคุณมีฟิลด์เพิ่มเติมในการทำให้เป็นอนุกรม
โจ

2
หมายเหตุ: "โดยทั่วไปแล้วคอนสตรัคเตอร์นี้ควรได้รับการปกป้องถ้าคลาสไม่ได้ถูกปิดผนึก" - ดังนั้นคอนสตรัคชั่นที่เป็นอนุกรมในตัวอย่างของคุณควรได้รับการปกป้อง (หรืออาจจะเหมาะสมกว่านั้น นอกเหนือจากนั้นทำงานได้ดี!
Daniel Fortunov

ข้อผิดพลาดอื่น ๆ อีกสองข้อในนี้: แอตทริบิวต์ [Serializable] เป็นสิ่งจำเป็น GetObjectData จะต้องโทรไปที่ฐาน GetObjectData
Daniel Fortunov

8

ใช้ ISerializable และทำตามรูปแบบปกติสำหรับการทำเช่นนี้

คุณต้องติดแท็กคลาสด้วยแอตทริบิวต์ [Serializable] และเพิ่มการสนับสนุนสำหรับอินเทอร์เฟซนั้นและเพิ่มตัวสร้างโดยนัย (อธิบายไว้ในหน้านั้นค้นหาการอนุมาน constructor ) คุณสามารถดูตัวอย่างการใช้งานได้ในโค้ดด้านล่างข้อความ


8

เพื่อเพิ่มคำตอบที่ถูกต้องข้างต้นฉันค้นพบว่าฉันสามารถหลีกเลี่ยงการทำสิ่งที่เป็นอนุกรมที่กำหนดเองนี้ถ้าฉันเก็บคุณสมบัติที่กำหนดเองของฉันในDataคอลเลกชันของExceptionชั้นเรียน

เช่น:

[Serializable]
public class JsonReadException : Exception
{
    // ...

    public string JsonFilePath
    {
        get { return Data[@"_jsonFilePath"] as string; }
        private set { Data[@"_jsonFilePath"] = value; }
    }

    public string Json
    {
        get { return Data[@"_json"] as string; }
        private set { Data[@"_json"] = value; }
    }

    // ...
}

อาจมีประสิทธิภาพในแง่ของประสิทธิภาพที่ต่ำกว่าโซลูชันของ Danielและอาจใช้ได้เฉพาะกับชนิด "integral" เช่นสตริงและจำนวนเต็มและอื่น ๆ

ยังคงเป็นเรื่องง่ายและเข้าใจได้มากสำหรับฉัน


1
นี่เป็นวิธีที่ดีและง่ายในการจัดการข้อมูลข้อยกเว้นเพิ่มเติมในกรณีที่คุณต้องเก็บไว้เพื่อการบันทึกหรือสิ่งที่ต้องการ หากคุณเคยมีความจำเป็นในการเข้าถึงค่าเพิ่มเติมเหล่านี้ในรหัสในการจับบล็อก แต่คุณก็จะอาศัยการรู้ปุ่มสำหรับค่าข้อมูลภายนอกที่ไม่ดีสำหรับการห่อหุ้ม ฯลฯ
คริสโตคิง

2
ว้าวขอบคุณ. ฉันสุ่มสูญเสียตัวแปรเพิ่มที่กำหนดเองทั้งหมดของฉันทุกครั้งที่มีข้อยกเว้นเกิดขึ้นอีกครั้งโดยใช้throw;และสิ่งนี้ได้รับการแก้ไข
Nyerguds

1
@ChristopherKing ทำไมคุณต้องรู้กุญแจ? พวกเขากำลัง hardcoded ในทะเยอทะยาน
Nyerguds

1

เคยมีบทความที่ยอดเยี่ยมจาก Eric Gunnerson บน MSDN "ข้อยกเว้นที่มีอารมณ์ดี" แต่ดูเหมือนว่าจะถูกดึงออกมา URL คือ:

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp08162001.asp

คำตอบของ Aydsman ถูกต้องข้อมูลเพิ่มเติมที่นี่:

http://msdn.microsoft.com/en-us/library/ms229064.aspx

ฉันไม่สามารถนึกถึงกรณีการใช้งานใด ๆ สำหรับข้อยกเว้นกับสมาชิกที่ไม่สามารถจัดลำดับได้ แต่ถ้าคุณหลีกเลี่ยงการพยายามทำให้เป็นอนุกรม / ดีซีเรียลไลซ์ใน GetObjectData และตัวสร้างการดีซีเรียลไลเซชันคุณควรตกลง และทำเครื่องหมายด้วยแอตทริบิวต์ [NonSerialized] ซึ่งเป็นเอกสารมากกว่าสิ่งอื่นใดเนื่องจากคุณกำลังใช้การทำให้เป็นอนุกรมด้วยตนเอง


0

ทำเครื่องหมายชั้นเรียนด้วย [Serializable] แต่ฉันไม่แน่ใจว่าสมาชิก IList จะได้รับการจัดการอย่างไรโดย serializer

แก้ไข

การโพสต์ด้านล่างถูกต้องเนื่องจากข้อยกเว้นที่กำหนดเองของคุณมีคอนสตรัคเตอร์ที่ใช้พารามิเตอร์

หากคุณใช้คอนสตรัคเตอร์เริ่มต้นและเปิดเผยสมาชิกแบบกำหนดเองสองรายการที่มีคุณสมบัติ getter / setter คุณสามารถหนีไปได้ด้วยการตั้งค่าแอททริบิวต์


-5

ฉันต้องคิดว่าการต้องการทำให้เป็นอันดับของข้อยกเว้นเป็นสิ่งบ่งชี้ว่าคุณกำลังทำสิ่งที่ผิด เป้าหมายสูงสุดคืออะไรที่นี่? หากคุณกำลังส่งข้อยกเว้นระหว่างสองกระบวนการหรือระหว่างการดำเนินการแยกกันของกระบวนการเดียวกันคุณสมบัติส่วนใหญ่ของข้อยกเว้นจะไม่ถูกต้องในกระบวนการอื่นต่อไป

มันอาจจะเหมาะสมกว่าที่จะดึงข้อมูลสถานะที่คุณต้องการที่คำสั่ง catch () และเก็บถาวร


9
Downvote - ข้อยกเว้นสถานะแนวทางของ Microsoft ควรเป็นแบบอนุกรมได้msdn.microsoft.com/en-us/library/ms229064.aspx เพื่อให้สามารถโยนข้ามขอบเขตของ appdomain เช่นใช้ remoting
โจ
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.