Pergunta

Eu tenho uma classe "Status" em C#, usada assim:

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

Você entendeu a ideia.Todos os chamadores de MyFunction deve verifique o status retornado:

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

Chamadores preguiçosos, entretanto, podem ignorar o status.

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

ou

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

É possível tornar isso impossível?por exemplo.uma exceção de lançamento

Em geral:é possível escrever uma classe C# na qual você ter chamar uma determinada função?

Na versão C++ da classe Status, posso escrever um teste em algum bool privado bIsChecked no destruidor e toca alguns sinos quando alguém não verifica esta instância.

Qual é a opção equivalente em C#?Eu li em algum lugar que "Você não quer um destruidor na sua classe C#"

O método Dispose da interface IDisposable é uma opção?

Neste caso não há recursos não gerenciados para liberar.Além disso, não está determinado quando o GC descartará o objeto.Quando ele for descartado, ainda será possível saber onde e quando você ignorou aquela instância específica do Status?A palavra-chave "using" ajuda, mas, novamente, não é obrigatório para chamadores preguiçosos.

Foi útil?

Solução

Tenho quase certeza de que você não pode obter o efeito desejado como valor de retorno de um método.C# simplesmente não pode fazer algumas das coisas que C++ pode.No entanto, uma maneira um tanto feia de obter um efeito semelhante é a seguinte:

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

No exemplo muito simples, você obtém uma instância de Toy chamando Parent.RequestToy, e passando um delegado.Em vez de retornar o brinquedo, o método chama imediatamente o delegado com o brinquedo, que deve chamar PutAway antes de retornar, ou o método RequestToy lançará uma exceção.Não faço nenhuma afirmação quanto à sabedoria de usar esta técnica - na verdade, em todos os exemplos de "algo deu errado", uma exceção é quase certamente uma aposta melhor - mas acho que é o mais próximo possível do seu pedido original.

Outras dicas

Sei que isso não responde diretamente à sua pergunta, mas se "algo deu errado" em sua função (circunstâncias inesperadas), acho que você deveria lançar uma exceção em vez de usar códigos de retorno de status.

Em seguida, deixe que o chamador capture e trate essa exceção, se puder, ou permita que ela se propague se o chamador não conseguir lidar com a situação.

A exceção lançada pode ser de um tipo personalizado, se for apropriado.

Para esperado resultados alternativos, concordo com a sugestão de @Jon Limjap.Gosto de um tipo de retorno bool e de prefixar o nome do método com "Try", como:

bool TryMyFunction(out Status status)
{
}

Se você realmente deseja exigir que o usuário recupere o resultado de MyFunction, você pode querer anulá-lo e usar uma variável out ou ref, por exemplo,

void MyFunction(out Status status)
{
}

Pode parecer feio, mas pelo menos garante que uma variável seja passada para a função que irá obter o resultado que você precisa.

@Ian,

O problema com exceções é que, se algo acontecer com muita frequência, você poderá estar gastando muitos recursos do sistema com a exceção.Uma exceção realmente deveria ser usada para excepcional erros, não mensagens totalmente esperadas.

Até mesmo System.Net.WebRequest lança uma exceção quando o código de status HTTP retornado é um código de erro.A maneira típica de lidar com isso é envolver um try/catch.Você ainda pode ignorar o código de status no bloco catch.

Você poderia, no entanto, ter um parâmetro Action< Status> para que o chamador seja forçado a passar uma função de retorno de chamada que aceite um status e depois verificar se ele o chamou.

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

Se você está preocupado com eles chamando IsOK() sem fazer nada, use Expression< Func< Status,bool>> e então você pode analisar o lambda para ver o que eles fazem com o status:

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

Ou renuncie completamente à classe de status e faça com que eles passem um delegado para sucesso e outro para erro:

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

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

Usar Status como valor de retorno me lembra dos "velhos tempos" da programação C, quando você retornava um número inteiro abaixo de 0 se algo não funcionasse.

Não seria melhor se você lançasse uma exceção quando (como você disse) algo deu errado?Se algum "código preguiçoso" não capturar sua exceção, você terá certeza.

Em vez de forçar alguém a verificar o status, acho que você deveria assumir que o programador está ciente dos riscos de não fazê-lo e tem um motivo para tomar essa atitude.Você não sabe como a função será usada no futuro e colocar uma limitação como essa apenas restringe as possibilidades.

Seria bom que o compilador verificasse isso, em vez de por meio de uma expressão.:/ Não vejo nenhuma maneira de fazer isso ...

Você pode lançar uma exceção:

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 ) { }
    }

A exceção acima é totalmente personalizável de acordo com suas necessidades.

Uma coisa que eu diria é o seguinte, deixaria para o chamador verificar o código de retorno, é responsabilidade dele apenas fornecer os meios e a interface.Além disso, é muito mais eficiente usar códigos de retorno e verificar o status com uma instrução if em vez de lançar exceções.Se realmente for um Excepcional circunstância, então jogue fora...mas digamos que se você não conseguiu abrir um dispositivo, talvez seja mais prudente seguir o código de retorno.

@Paul você poderia fazer isso em tempo de compilação C# extensível.

O CCG tem um warn_unused_result atributo que é ideal para esse tipo de coisa.Talvez os compiladores da Microsoft tenham algo semelhante.

Um padrão que às vezes pode ser útil se o objeto para o qual o código emite solicitações for usado apenas por um único thread(*) é fazer com que o objeto mantenha um estado de erro e dizer que se uma operação falhar, o objeto ficará inutilizável até o o estado de erro é redefinido (solicitações futuras devem falhar imediatamente, de preferência lançando uma exceção imediata que inclua informações sobre a falha anterior e a nova solicitação).Nos casos em que o código de chamada antecipa um problema, isso pode permitir que o código de chamada lide com o problema de maneira mais limpa do que se uma exceção fosse lançada;problemas que não são ignorados pelo código de chamada geralmente acabarão acionando uma exceção logo após ocorrerem.

(*) Se um recurso for acessado por vários threads, crie um objeto wrapper para cada thread e faça com que as solicitações de cada thread passem por seu próprio wrapper.

Este padrão é utilizável mesmo em contextos onde as exceções não o são, e às vezes pode ser muito prático nesses casos.Em geral, entretanto, alguma variação do padrão try/do costuma ser melhor.Faça com que os métodos gerem exceção em caso de falha, a menos que o chamador indique explicitamente (usando um TryXX método) que falhas são esperadas.Se os chamadores dizem que falhas são esperadas, mas não as tratam, o problema é deles.Pode-se combinar o try/do com uma segunda camada de proteção usando o esquema acima, mas não tenho certeza se valeria a pena o custo.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top