문제

C#에는 다음과 같이 사용되는 "상태" 클래스가 있습니다.

Status MyFunction()
{
   if(...) // something bad
     return new Status(false, "Something went wrong")
   else
     return new Status(true, "OK");
}

당신은 아이디어를 얻습니다.MyFunction의 모든 호출자 ~해야 한다 반환된 상태를 확인하세요.

Status myStatus = MyFunction();
if ( ! myStatus.IsOK() )
   // handle it, show a message,...

그러나 게으른 호출자는 상태를 무시할 수 있습니다.

MyFunction(); // call function and ignore returned Status

또는

{
  Status myStatus = MyFunction(); 
} // lose all references to myStatus, without calling IsOK() on it

이것을 불가능하게 만드는 것이 가능합니까?예를 들어예외 발생

일반적으로:당신이 사용하는 C# 클래스를 작성하는 것이 가능합니까? 가지다 특정 기능을 호출하려면?

Status 클래스의 C++ 버전에서는 다음의 일부 비공개 bool bIsChecked에 대한 테스트를 작성할 수 있습니다. 오물 소각로 누군가가 이 인스턴스를 확인하지 않으면 벨을 울립니다.

C#에서 동등한 옵션은 무엇입니까?"C# 클래스에 소멸자를 원하지 않습니다"라는 내용을 어딘가에서 읽었습니다.

IDisposable 인터페이스의 Dispose 메서드가 옵션입니까?

이 경우 해제할 관리되지 않는 리소스가 없습니다.게다가 확정된 것도 아니고 언제 GC가 개체를 삭제합니다.결국 폐기되면 특정 Status 인스턴스를 언제 어디서 무시했는지 알 수 있습니까?"using" 키워드는 도움이 되지만 다시 말하지만 그렇지 않습니다. 필수의 게으른 호출자를 위해.

도움이 되었습니까?

해결책

나는 당신이 메소드의 반환 값으로 원하는 효과를 얻을 수 없다고 확신합니다.C#은 C++에서 할 수 있는 일 중 일부를 수행할 수 없습니다.그러나 유사한 효과를 얻는 다소 추악한 방법은 다음과 같습니다.

using System;

public class Example
{
    public class Toy
    {
        private bool inCupboard = false;
        public void Play() { Console.WriteLine("Playing."); }
        public void PutAway() { inCupboard = true; }
        public bool IsInCupboard { get { return inCupboard; } }
    }

    public delegate void ToyUseCallback(Toy toy);

    public class Parent
    {
        public static void RequestToy(ToyUseCallback callback)
        {
            Toy toy = new Toy();
            callback(toy);
            if (!toy.IsInCupboard)
            {
                throw new Exception("You didn't put your toy in the cupboard!");
            }
        }
    }

    public class Child
    {
        public static void Play()
        {
            Parent.RequestToy(delegate(Toy toy)
            {
                toy.Play();
                // Oops! Forgot to put the toy away!
            });
        }
    }

    public static void Main()
    {
        Child.Play();
        Console.ReadLine();
    }
}

매우 간단한 예에서는 Parent.RequestToy를 호출하여 Toy 인스턴스를 얻습니다. 그리고 그것을 대리자에게 전달합니다..장난감을 반환하는 대신 메서드는 즉시 장난감이 포함된 대리자를 호출합니다. 이 대리자는 반환되기 전에 PutAway를 호출해야 합니다. 그렇지 않으면 RequestToy 메서드가 예외를 발생시킵니다.나는 이 기술을 사용하는 것이 현명하다고 주장하지 않습니다. 실제로 모든 "무언가 잘못되었습니다"의 예에서는 예외가 거의 확실하게 더 나은 선택입니다. 그러나 원래 요청에 도달할 수 있는 만큼 가깝다고 생각합니다.

다른 팁

이것이 귀하의 질문에 직접적으로 대답할 수 없다는 것을 알고 있지만, 귀하의 함수 내에서 "무언가 잘못되었습니다"(예기치 않은 상황)인 경우 상태 반환 코드를 사용하는 대신 예외를 발생시켜야 한다고 생각합니다.

그런 다음 가능한 경우 이 예외를 포착하고 처리하도록 호출자에게 맡기거나 호출자가 상황을 처리할 수 없는 경우 전파하도록 허용합니다.

적절한 경우 던져진 예외는 사용자 정의 유형일 수 있습니다.

을 위한 예상되는 대체 결과는 @Jon Limjap의 제안에 동의합니다.나는 bool 반환 유형을 좋아하고 메소드 이름 앞에 "Try"를 붙이는 것을 좋아합니다.

bool TryMyFunction(out Status status)
{
}

사용자가 MyFunction의 결과를 검색하도록 요구하려면 대신 이를 무효화하고 out 또는 ref 변수를 사용할 수 있습니다. 예:

void MyFunction(out Status status)
{
}

보기 흉해 보일 수도 있지만 최소한 필요한 결과를 가져오는 함수에 변수가 전달되도록 보장합니다.

@이안,

예외의 문제는 너무 자주 발생하는 경우 예외에 너무 많은 시스템 리소스를 소비할 수 있다는 것입니다.예외는 실제로 다음과 같은 경우에 사용해야 합니다. 특별한 오류, 완전히 예상된 메시지는 아닙니다.

반환된 HTTP 상태 코드가 오류 코드인 경우 System.Net.WebRequest에서도 예외가 발생합니다.이를 처리하는 일반적인 방법은 주위에 try/catch를 래핑하는 것입니다.catch 블록의 상태 코드는 계속 무시할 수 있습니다.

그러나 Action< Status> 매개변수를 사용하면 호출자가 상태를 허용하는 콜백 함수를 강제로 전달한 다음 이를 호출했는지 확인하도록 할 수 있습니다.

void MyFunction(Action<Status> callback)
 { bool errorHappened = false;

   if (somethingBadHappend) errorHappened = true;

   Status status = (errorHappend)
                     ? new Status(false, "Something went wrong")
                     : new Status(true, "OK");
   callback(status)

   if (!status.isOkWasCalled) 
     throw new Exception("Please call IsOK() on Status"). 
 }

MyFunction(status => if (!status.IsOK()) onerror());

아무것도 하지 않고 IsOK()를 호출하는 것이 걱정된다면 대신 Expression< Func< Status,bool>>을 사용한 다음 람다를 분석하여 상태를 어떻게 처리하는지 확인할 수 있습니다.

void MyFunction(Expression<Func<Status,bool>> callback)
 { if (!visitCallbackExpressionTreeAndCheckForIsOKHandlingPattern(callback))
     throw new Exception
                ("Please handle any error statuses in your callback");


   bool errorHappened = false;

   if (somethingBadHappend) errorHappened = true;

   Status status = (errorHappend)
                     ? new Status(false, "Something went wrong")
                     : new Status(true, "OK");

   callback.Compile()(status);
 }

MyFunction(status => status.IsOK() ? true : onerror());

또는 상태 클래스를 모두 포기하고 성공을 위해 하나의 대리자를 전달하고 오류를 위해 다른 대리자를 전달하도록 합니다.

void MyFunction(Action success, Action error)
 { if (somethingBadHappened) error(); else success();
 }

MyFunction(()=>;,()=>handleError()); 

Status를 반환 값으로 사용하면 뭔가가 작동하지 않을 때 0 미만의 정수를 반환했던 C 프로그래밍의 "옛날"이 생각납니다.

(당신이 말했듯이) 예외를 던지면 더 좋지 않을까요? 뭔가 잘못됐어?일부 "게으른 코드"가 예외를 포착하지 못하는 경우 확실히 알 수 있습니다.

누군가에게 상태를 확인하도록 강요하기보다는 프로그래머가 그렇게 하지 않는 데 따른 위험을 인식하고 그러한 조치를 취하는 이유가 있다고 가정해야 한다고 생각합니다.기능이 미래에 어떻게 사용될지 알 수 없으며 그러한 제한을 가하는 것은 가능성을 제한할 뿐입니다.

표현식을 통해서보다는 컴파일러가 이를 확인하도록 하는 것이 확실히 좋을 것입니다.:/ 그래도 그렇게 할 방법이 보이지 않습니다 ...

다음을 통해 예외를 발생시킬 수 있습니다.

throw MyException;


[global::System.Serializable]
        public class MyException : Exception
        {
        //
        // For guidelines regarding the creation of new exception types, see
        //    http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconerrorraisinghandlingguidelines.asp
        // and
        //    http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp07192001.asp
        //

        public MyException () { }
        public MyException ( string message ) : base( message ) { }
        public MyException ( string message, Exception inner ) : base( message, inner ) { }
        protected MyException (
          System.Runtime.Serialization.SerializationInfo info,
          System.Runtime.Serialization.StreamingContext context )
            : base( info, context ) { }
    }

위의 예외는 요구 사항에 맞게 완전히 사용자 정의할 수 있습니다.

내가 말하고 싶은 한 가지는 반환 코드를 확인하는 것은 호출자에게 맡길 것이며, 수단과 인터페이스를 제공하는 것은 호출자의 책임이라는 것입니다.또한 예외를 처리하는 것보다 반환 코드를 사용하고 if 문으로 상태를 확인하는 것이 훨씬 더 효율적입니다.정말 그렇다면 특별한 상황이 그렇다면 꼭 버리세요...하지만 장치를 열지 못한 경우에는 반환 코드를 고수하는 것이 더 현명할 수 있습니다.

@Paul 컴파일 타임에 할 수 있습니다 확장 가능한 C#.

GCC에는 warn_unused_result 이런 종류의 일에 이상적인 속성입니다.아마도 Microsoft 컴파일러에도 비슷한 것이 있을 것입니다.

코드가 요청을 발행하는 객체가 단일 스레드(*)에 의해서만 사용될 경우 때때로 도움이 될 수 있는 패턴 중 하나는 객체가 오류 상태를 유지하도록 하고 작업이 실패하면 해당 객체를 사용할 수 없게 되는 것입니다. 오류 상태가 재설정됩니다(향후 요청은 즉시 실패해야 하며, 이전 실패와 새 요청에 대한 정보가 모두 포함된 즉각적인 예외를 발생시키는 것이 좋습니다).호출 코드에서 문제가 발생할 것으로 예상되는 경우 호출 코드는 예외가 발생한 경우보다 문제를 더 명확하게 처리할 수 있습니다.호출 코드에서 무시되지 않는 문제는 일반적으로 문제가 발생한 직후에 예외를 유발하게 됩니다.

(*) 여러 스레드에서 리소스에 액세스하는 경우 각 스레드에 대한 래퍼 개체를 만들고 각 스레드의 요청이 자체 래퍼를 통과하도록 합니다.

이 패턴은 예외가 발생하지 않는 상황에서도 사용할 수 있으며 때로는 그러한 경우에 매우 실용적일 수 있습니다.그러나 일반적으로 시도/실행 패턴을 약간 변형하는 것이 더 좋습니다.호출자가 명시적으로 표시하지 않는 한( TryXX 방법) 실패가 예상됩니다.호출자가 실패가 예상된다고 말했지만 이를 처리하지 않는다면 그것이 문제입니다.위의 구성표를 사용하여 시도/실행을 두 번째 보호 계층과 결합할 수 있지만 비용을 지불할 가치가 있는지 확실하지 않습니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top