programing

사용자 지정 .NET 예외를 직렬화할 수 있도록 만드는 올바른 방법은 무엇입니까?

starjava 2023. 6. 1. 21:38
반응형

사용자 지정 .NET 예외를 직렬화할 수 있도록 만드는 올바른 방법은 무엇입니까?

보다 구체적으로, 예외 자체가 직렬화되거나 직렬화되지 않을 수 있는 사용자 지정 개체를 포함하는 경우입니다.

다음 예를 들어 보겠습니다.

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.

사용자 지정 예외에 대한 직렬화를 구현하기 위한 공통 코드 패턴이 있습니까?

사용자 지정 속성 없이 기본 구현

직렬화 가능예외.WithoutCustomProperties.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. 파생 클래스를 속성으로 장식해야 합니다. 이 속성은 기본 클래스에서 상속되지 않으며 지정하지 않으면 직렬화가 실패하고SerializationException"어셈블리 Y의 유형 X는 직렬화 가능한 것으로 표시되지 않습니다."라고 명시합니다.
  2. 사용자 지정 직렬화를 구현해야 합니다.[Serializable]. — 속만으지않습니다하충분는로성.ExceptionISerializable즉, 파생된 클래스도 사용자 지정 직렬화를 구현해야 합니다.여기에는 두 가지 단계가 포함됩니다.
    1. 직렬화 생성자를 제공합니다.이 생성자는 다음과 같아야 합니다.private의 수업이 만당수업이의신약▁your라면.sealed그렇지 않으면 그래야 합니다.protected파생 클래스에 대한 액세스를 허용합니다.
    2. GetObjectData()를 재정의하고 에 전화를 걸어야 합니다.base.GetObjectData(info, context)마지막에, 기본 클래스가 자신의 상태를 저장하도록 하기 위해.

직렬화 가능예외.WithCustomProperties.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);
        }
    }
}

파생 직렬화 가능예외.WithAdditionalCustomProperties.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);
        }
    }
}

단위 테스트

위에 정의된 세 가지 예외 유형에 대한 MST 검정 단위 검정.

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()");
        }
    }
}

는 이미 번호로할 수 , 이를 .GetObjectData변수를 저장하고 개체를 다시 수화할 때 호출할 수 있는 생성자를 제공하는 방법입니다.

예를 들어 다음과 같습니다.

[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>));
    }

}

위의 정답을 추가하기 위해 사용자 지정 속성을 의 컬렉션에 저장하면 이러한 사용자 지정 일련화 작업을 수행하지 않을 수 있습니다.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이 제공하는 솔루션보다 성능 면에서 효율성이 떨어지고 문자열이나 정수와 같은 "통합" 유형에만 사용할 수 있습니다.

여전히 그것은 저에게 매우 쉽고 이해하기 쉬웠습니다.

ISerializable을 구현하고 이를 위한 일반적인 패턴을 따릅니다.

클래스에 [Serializable] 특성을 태그하고 해당 인터페이스에 대한 지원을 추가하고 암시적 생성자를 추가해야 합니다(이 페이지에 설명된 에 대한 검색은 생성자를 의미함).텍스트 아래의 코드에서 구현의 예를 볼 수 있습니다.

예전에 MSDN "성질 좋은 예외"에 대한 에릭 거너슨의 훌륭한 기사가 있었지만, 철회된 것 같습니다.URL:

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

Aydsman의 대답은 정확합니다. 자세한 내용은 여기에 있습니다.

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

직렬화할 수 없는 멤버가 있는 예외에 대한 사용 사례는 생각할 수 없지만 GetObjectData와 역직렬화 생성자에서 이러한 멤버를 직렬화/직렬화하려는 시도를 피한다면 괜찮을 것입니다.또한 직렬화를 직접 구현하는 경우에는 [NonSerialized](비직렬화) 특성으로 표시합니다.

.NET Core에서 .Net 5.0 이상에서는 Microsoft가 BinaryFormatter에서 발견된 보안 위협 관행을 따르기 때문에 Serialable을 사용하지 않습니다.

데이터 수집에 저장하는 예제 사용

IList 구성원이 serializer에서 얼마나 잘 처리될지는 모르겠지만 클래스를 [Serializable](시리얼화 가능)로 표시합니다.

편집

아래 게시물은 정확합니다. 사용자 지정 예외에 매개 변수를 사용하는 생성자가 있으므로 ISerializable을 구현해야 합니다.

기본 생성자를 사용하고 getter/setter 속성으로 두 사용자 지정 멤버를 노출시킨 경우 속성을 설정하는 것만으로도 문제를 해결할 수 있습니다.

저는 예외를 연재하고 싶어하는 것은 여러분이 무언가에 대해 잘못된 접근을 하고 있다는 강력한 표시라고 생각해야 합니다.여기서 궁극적인 목표는 무엇입니까?두 프로세스 간에 또는 동일한 프로세스의 개별 실행 간에 예외를 전달하는 경우에는 다른 프로세스에서 대부분의 예외 속성을 사용할 수 없습니다.

catch() 문에서 원하는 상태 정보를 추출하여 보관하는 것이 더 합리적일 수 있습니다.

언급URL : https://stackoverflow.com/questions/94488/what-is-the-correct-way-to-make-a-custom-net-exception-serializable

반응형