Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

c# how to 'return if not null' in a one liner?

Tags:

c#

Is there a one liner to return something if it not null or continue execution, or how to make such thing? All this to avoid copy pasta the IF lines in several methods.

Initial code would be this:

var error = ValidateStuff(someArg);
if (error != null)
{
    return error;
}
DoOtherStuff();

So how to refactor it in order to avoid copy pasting that same if everywhere? Pseudo code would be something like

ValidateStuff(someArg) ? return ___ : continue;
DoSomethingElse();
AndMoreStuff();

-EDIT- An even more simplistic example, in order to clear some doubts going on in some answers and comments:

public string Foo(string arg)
{
    string fuu = GetMeSomething(arg);

    if(fuu != null) return fuu;

    ImDoingThings();

    return "I did things";
}

Would be awesome to have this:

public string Foo(string arg)
{
    ReturnIfNotNull(GetMeSomething(arg));

    ImDoingThings();

    return "I did things.";
}
like image 950
Gotham Llianen Avatar asked Apr 21 '17 14:04

Gotham Llianen


People also ask

What C is used for?

C programming language is a machine-independent programming language that is mainly used to create many types of applications and operating systems such as Windows, and other complicated programs such as the Oracle database, Git, Python interpreter, and games and is considered a programming foundation in the process of ...

What is C in C language?

What is C? C is a general-purpose programming language created by Dennis Ritchie at the Bell Laboratories in 1972. It is a very popular language, despite being old. C is strongly associated with UNIX, as it was developed to write the UNIX operating system.

What is the full name of C?

In the real sense it has no meaning or full form. It was developed by Dennis Ritchie and Ken Thompson at AT&T bell Lab. First, they used to call it as B language then later they made some improvement into it and renamed it as C and its superscript as C++ which was invented by Dr.

Is C language easy?

Compared to other languages—like Java, PHP, or C#—C is a relatively simple language to learn for anyone just starting to learn computer programming because of its limited number of keywords.


2 Answers

Sure:

void ValidateStuff(someArg) { 
    if (!validation(someArg)) { 
        throw new ValidationException("Whatever went wrong...", errorDetails);
    }
}

And in your code:

ValidateStuff(someArg);
DoOtherStuff();

P.S.: I often combine generic code in ValidateStuff with #if (DEBUG) [...] #else [...] #endif, so that production irrelevant stuff doesn't end up in production binaries.


What about warnings?

I use a few tricks for that:

  1. Build an error object only if you really need it.
  2. Similarly, build the list of errors only when something fails.
  3. Make use of 'using' for easy coding. I'm a lazy coder... This is a small risk though; if you forget to use using, you're in trouble... Still, I think this risk is better than the alternative of "let's go on with the show and forget there was something warning in the first place".
  4. If you have a better handler for your warnings (so: instead of the exception), use it and be done with it.
  5. If an error occurs, throw the lot.

Obviously you can extend it as you see fit...

Without further due:

public class WarningsHandler : IDisposable
{
    private List<WarningErrorBase> errors = null;

    // Default handler. Remember to use 'using', or otherwise you'll end up 
    // with pain and suffering!
    public void Dispose()
    {
        var errors = FetchValidationResults();

        if (errors != null && errors.Count > 0)
        {
            throw new ValidationException(errors);
        }
    }

    // Handler if you have a better idea than using an Exception
    public IEnumerable<Error> FetchValidationResults() 
    {
        var errors = this.errors;
        this.errors = null;
        return errors;
    }

    public void Warn(bool condition, Func<Warning> errorBuilder)
    {
        if (condition) 
        { 
            if (errors == null) { errors = new List<WarningErrorBase>(); }
            errors.Add(errorBuilder()); 
        }
    }

    public void Error(bool condition, Func<Error> errorBuilder)
    {
        if (condition) 
        { 
            if (errors == null) { errors = new List<WarningErrorBase>(); }
            errors.Add(errorBuilder()); 

            throw new ValidationException(FetchValidationResults());
        }
    }
}

How to use it?

void MyThing()
{
    using (var handler = new WarningsHandler())
    {
        handler.Error(foo == null, "Foo must have a value");
        handler.Warn(foo.Count > 2, () => new Warning("You should have less than 2 foo's present.");
        // etc.
    }
} 

Okay, just one more trick. :-)

A last way to mix different error messages with little overhead is to use yield return. This enables you to return multiple result values with different behaviors. Null values can be ignored trivially.

First we need a whole bunch of wrappers for this:

// We need some base interface that we can use for return values
public interface IResult { }

// We have to wrap normal return values
public class Result<T> : IResult
{
    public Result(T result) { this.Value = result; }

    public T Value { get; private set; }
}

// A few classes for messages, errors, warnings, ...
public class Message : IResult
{
    public Message(string format, params object[] args)
    {
        this.Text = string.Format(format, args);
    }

    public string Text { get; private set; }

    internal virtual void Handle(List<Message> messages)
    {
        messages.Add(this);
    }
}

public class Error : Message
{
    public Error(Exception ex) :
        base("Uncaught exception: {0}", ex.Message)
    { }

    public Error(string format, params object[] args) : 
        base(format, args)
    { }

    internal override void Handle(List<Message> messages)
    {
        throw new ValidationException(this.Text);
    }
}

// Other wrappers like warnings, etc. 
// Wrapping IEnumerable<IResult> is useful too.

Next, we need some helper method to execute our methods that now return an IEnumerable instead of a normal type. For that, I add a helper class, which basically handles the execution, unwrapping and return values.

public static class ExecutionEngine
{
    public static T Execute<T>(this IEnumerable<IResult> method)
    {
        List<Message> messages = new List<Message>();
        try
        {
            foreach (var item in method)
            {
                // yield return null is ignored here:
                if (item != null)
                {
                    // Handle validation results, messages, etc
                    Message msg = item as Message;
                    if (msg != null)
                    {
                        msg.Handle(messages);
                    }

                    Result<T> returnValue = item as Result<T>;
                    if (returnValue != null)
                    {
                        return returnValue.Value;
                    }

                    // handle other things, error if something's wrong
                }
            }

            throw new Exception("Method finished without a return value.");
        }
        catch (ValidationException)
        {
            // TODO: handle messages?

            throw;
        }
        catch (Exception ex)
        {
            // TODO: handle messages?

            var error = new Error(ex);
            error.Handle(messages);

            throw; // unreachable because Error throws. This is to make sure it all compiles
        }
    }
}

Once we have all that in order, the code itself becomes pretty simple, and resembles a lot like you would do normally. The main difference is that you simply add 'yield return' everywhere, sometimes with an additional wrapper:

public IEnumerable<IResult> MyMethod()
{
    // Delegate validation to somewhere else. You might wrap an IEnumerable<IResult> here:
    yield return ValidateStuff(someArg);

    // Information messages, etc
    yield return new Message("Hello world!");

    // You might end up with an Exception you didn't expect...
    var tmp = new List<int>();
    tmp[2] = 2; // oopz...

    // ...
    yield return new Result<int>(12); // return 12;
}

The only thing remains is that you cannot call MyMethod anymore. This is easily fixed by using the execution engine:

int result = MyMethod().Execute<int>();
like image 159
atlaste Avatar answered Oct 10 '22 06:10

atlaste


First, you're likely going about this in the wrong way; you're probably going to be much better off throwing exceptions rather than trying to return error codes.

But ... you can make something like your pseudo-code work using lambdas. It gets messy (and would be even messier with generics), but for completeness and answering your specific question:

static Error ReturnIf(Error error,
    Func<Error, bool> predicate,
    Func<Error> rest)
{
    return predicate(error) ? error : rest();
}

static Error Test2(bool someArg)
{
    return ReturnIf(ValidateStuff(someArg), error => error != null, () =>
    {
        DoSomethingElse();
        AndMoreStuff();
        return null;
    });
}

Or, since you've actually said "return if not null", the code an be simplified and more specific:

static Error ReturnIfNotNull(Error error,
       Func<Error> rest)
{
    return error ?? rest();
}

static Error Test2(bool someArg)
{
    return ReturnIfNotNull(ValidateStuff(someArg) () =>
    {
        DoSomethingElse();
        AndMoreStuff();
        return null;
    });
}

I'm not suggesting you would actually want to use code such as this in your particular situation ... Although Bart de Smet explored this technique extensively.

like image 38
Ðаn Avatar answered Oct 10 '22 05:10

Ðаn