Tuesday, September 1, 2015

Functional C #: Non-zero reference types

This article is the third in a series of "Functional C #":


Functional C #: Immutable objects
Functional C #: Obsession primitives
Functional C #: Non-zero reference types
Functional C #: Exception Handling
The non-zero reference types in C #: introduction
Look at the example below:

Customer customer = _repository.GetById (id);
Console.WriteLine (customer.Name);
Something familiar, huh? But what mistakes do you see?

The problem is that we do not know for certain whether the method returns a non-zero GetById instance. In spite of everything, there is a definite chance that we will get null. In this case, we get an exception NullReferenceException. The situation could be even worse if, before use of variable customer, it has not been initialized by GetById. In this case, it will be very difficult to catch such an error.

The faster we get feedback, the less time we have to correct the error. Of course, the fastest possible response we can only get from the compiler. Agree, it would be great nappisat our code and let the compiler do all the necessary checks?

Customer! customer = _repository.GetById (id);
Console.WriteLine (customer.Name);
Where Customer! It is a non-zero type, ie instances of this class can not be null in principle. Do you think it would be cool to be sure that the compiler will tell us if a piece of code can return a null?

Of course, it would be cool! Or even better:

Customer customer = _repository.GetById (id);
Console.WriteLine (customer.Name);
That is, to make all access types default non-zero (as well as the value type). And if we want to apply a zero type, you should use this code:

Customer? customer = _repository.GetById (id);
Console.WriteLine (customer.Name);
Can you imagine a world without all those annoying NullReferenceException? I can not too.

Unfortunately neobnulyaemye access types can not be entered in C # language as a sign. To learn more about this topic, I recommend reading this article to:

Article Eric Lippert (Eric Lippert)
Interesting, but practically unrealizable "design" solution
But do not worry. Although we can not force the compiler to help us and use the power of non-zero reference types, there are workarounds, which now have recourse. Let's look at the class Customer, we wrote in a previous article:

public class Customer
{
    public CustomerName Name {get; private set; }
    public Email Email {get; private set; }
    public Customer (CustomerName name, Email email)
    {
        if (name == null)
            throw new ArgumentNullException ("name");
        if (email == null)
            throw new ArgumentNullException ("email");
        Name = name;
        Email = email;
    }
    public void ChangeName (CustomerName name)
    {
        if (name == null)
            throw new ArgumentNullException ("name");
        Name = name;
    }
    public void ChangeEmail (Email email)
    {
        if (email == null)
            throw new ArgumentNullException ("email");
        Email = email;
    }
}
As you can see, we have moved the field class Customer (name and email) in separate classes. However, we can not do anything with checks on null. These are the only conditions that remain in the class Customer.

How to get rid of checks for null
So how can we get rid of these checks? Of course, using rewriting IL (Intermediate Language Rewrite)!

There is a wonderful package called NuGet NullGuard.Fody. First you need to download and install. Mark the assembly this attribute:

[assembly: NullGuard (ValidationFlags.All)]

What have we done? From now on, every method and property of the assembly is automatically checked for null. Now, we can rewrite the class Customer. Look it is simple and elegant:

public class Customer
{
    public CustomerName Name {get; private set; }
    public Email Email {get; private set; }
    public Customer (CustomerName name, Email email)
    {
        Name = name;
        Email = email;
    }
    public void ChangeName (CustomerName name)
    {
        Name = name;
    }
    public void ChangeEmail (Email email)
    {
        Email = email;
    }
}
Or even simpler:

public class Customer
{
    public CustomerName Name {get; set; }
    public Email Email {get; set; }
    public Customer (CustomerName name, Email email)
    {
        Name = name;
        Email = email;
    }
}
Despite the visual simplicity of the class, in fact, it looks like this:

public class Customer
{
    private CustomerName _name;
    public CustomerName Name
    {
        get
        {
            CustomerName customerName = _name;
            if (customerName == null)
                throw new InvalidOperationException ();
            return customerName;
        }
        set
        {
            if (value == null)
                throw new ArgumentNullException ();
            _name = value;
        }
    }
    private Email _email;
    public Email Email
    {
        get
        {
            Email email = _email;
            if (email == null)
                throw new InvalidOperationException ();
            return email;
        }
        set
        {
            if (value == null)
                throw new ArgumentNullException ();
            _email = value;
        }
    }
    public Customer (CustomerName name, Email email)
    {
        if (name == null)
            throw new ArgumentNullException ("name", "[NullGuard] name is null.");
        if (email == null)
            throw new ArgumentNullException ("email", "[NullGuard] email is null.");
        Name = name;
        Email = email;
    }
}
But what about the value null?
So how can we determine what the value of a certain type can be empty (null)? To do this, we will use Monad Maybe.

public struct Maybe <T>
{
    private readonly T _value;
    public T Value
    {
        get
        {
            Contracts.Require (HasValue);
            return _value;
        }
    }
    public bool HasValue
    {
        get {return _value! = null; }
    }
    public bool HasNoValue
    {
        get {return! HasValue; }
    }
    private Maybe ([AllowNull] T value)
    {
        _value = value;
    }
    public static implicit operator Maybe <T> ([AllowNull] T value)
    {
        return new Maybe <T> (value);
    }
}
As you can see, the input values ​​for the class attribute marked Maybe AllowNull. Now we can write the following code using Maybe:

1
Maybe <Customer> customer = _repository.GetById (id);
And now it is evident that the method GetById can return a null value. Also, now you can not accidentally mix up the value which can accept null, neobnulyaemym a value that would lead to a compiler error.

Maybe <Customer> customer = _repository.GetById (id);
ProcessCustomer (customer); // Compiler Error
private void ProcessCustomer (Customer customer)
{
    // Method body
}
Of course, you now have to decide which package assembly will be processed NullGuard.Fody. Perhaps the use of this package in WPF is not a good idea because there there are many system components that are essentially zero. That's why adding checks for null does not give you special benefits. However, for all other assemblies this method would be more than relevant.

A small note on the Monad Maybe. You might want to call it Option due to the naming conventions of language F #. I personally prefer to use Maybe, but in my opinion, the distribution of programming in this issue about 50 to 50. Of course, it's just a matter of taste.

What about static checks?
Okay, quick feedback during execution is good, but it still feedback during execution. It would be great to analyze the code even faster - say, at compile time.

The answer lies in the attribute NotNull wonderful addition to Visual Studio - ReSharper. NotNull attribute applied to any method and returning it a value of zero, you receive a warning from the plugin ReSharper.

This method can greatly facilitate your life, but it suffers from some problems.

First, the method works ReSharper reverse. Now you need to mark the attribute NotNull those values ​​that can not be zero. It would be much easier to mark this attribute values ​​that, on the contrary, would be zero. The rest would be considered a default neobnulyaemymi.

Second, the prevention - it is just a warning. You can simply not pay attention to them and miss them. Of course, we can install Visual Studio settings so that it will understand the warnings as errors. Maybe monad However, you are much less likely to make a mistake.

It is for these reasons that I do not use ReSharper, although they are very helpful.

Conclusion
The approach described above is indeed very powerful:

You get to catch a bug with zero. Now, you will not be bored permanent NullReferenceException.
You will increase the readability of the code. Now do not be everywhere constant flickering check for null values ​​before using the object.
By default, all your techniques will be protected by an exception NullReferenceException. And you will not have to tag every new method attribute NotNull.
And that's it! In the next article we we will consider exception handling functional C #.

By Unknown with No comments

0 коммент.:

Post a Comment