Magic method signatures in C #

I present to your attention the translation of the article The Magical Methods in C # by CEZARY PIĄTEK .



There is a specific set of method signatures in C # that have language level support. Methods with such signatures allow you to use a custom syntax with all its advantages. For example, they can be used to simplify our code or create a DSL in order to express a solution to a problem in a more beautiful way. I see such methods all over the place, so I decided to write a post and summarize all my findings on this topic, namely:



  • Collection initialization syntax
  • Dictionary initialization syntax
  • Deconstructors
  • Custom awaitable types
  • The query expression pattern


Collection initialization syntax



The collection initialization syntax is a rather old feature, because it exists with C # 3.0 (released at the end of 2007). Let me remind you that the collection initialization syntax allows you to create a list with elements in one block:



var list = new List<int> { 1, 2, 3 };


This code is equivalent to the one below:



var temp = new List<int>();
temp.Add(1);
temp.Add(2);
temp.Add(3);
var list = temp;


BCL. , :



  • IEnumerable
  • void Add(T item)


public class CustomList<T>: IEnumerable
{
    public IEnumerator GetEnumerator() => throw new NotImplementedException();
    public void Add(T item) => throw new NotImplementedException();
}


, Add :



public static class ExistingTypeExtensions
{
    public static void Add<T>(this ExistingType @this, T item) => throw new NotImplementedException();
}


- :



class CustomType
{
    public List<string> CollectionField { get; private set; }  = new List<string>();
}

class Program
{
    static void Main(string[] args)
    {
        var obj = new CustomType
        {
            CollectionField =
            {
                "item1",
                "item2"
            }
        };
    }
}


. ? :



var obj = new CustomType
{
    CollectionField =
    {
        { existingItems }
    }
};


, :



  • IEnumerable
  • void Add(IEnumerable<T> items)


public class CustomList<T>: IEnumerable
{
    public IEnumerator GetEnumerator() => throw new NotImplementedException();
    public void Add(IEnumerable<T> items) => throw new NotImplementedException();
}


, BCL void Add(IEnumerable<T> items), , :



public static class ListExtensions
{
    public static void Add<T>(this List<T> @this, IEnumerable<T> items) => @this.AddRange(items);
}


:



var obj = new CustomType
{
    CollectionField =
    {
        { existingItems.Where(x => /*Filter items*/).Select(x => /*Map items*/) }
    }
};


(IEnumerable):



var obj = new CustomType
{
    CollectionField =
    {
        individualElement1,
        individualElement2,
        { list1.Where(x => /*Filter items*/).Select(x => /*Map items*/) },
        { list2.Where(x => /*Filter items*/).Select(x => /*Map items*/) },
    }
};


.



, -, protobuf. , protobuf: grpctools .NET proto, - :



[DebuggerNonUserCode]
public RepeatableField<ItemType> SomeCollectionField
{
    get
    {
        return this.someCollectionField_;
    }
}


, - , RepeatableField void Add(IEnumerable items), - :



/// <summary>
/// Adds all of the specified values into this collection. This method is present to
/// allow repeated fields to be constructed from queries within collection initializers.
/// Within non-collection-initializer code, consider using the equivalent <see cref="AddRange"/>
/// method instead for clarity.
/// </summary>
/// <param name="values">The values to add to this collection.</param>
public void Add(IEnumerable<T> values)
{
    AddRange(values);
}




C# 6.0 — , . :



var errorCodes = new Dictionary<int, string>
{
    [404] = "Page not Found",
    [302] = "Page moved, but left a forwarding address.",
    [500] = "The web server can't come out to play today."
};


:



var errorCodes = new Dictionary<int, string>();
errorCodes[404] = "Page not Found";
errorCodes[302] = "Page moved, but left a forwarding address.";
errorCodes[500] = "The web server can't come out to play today.";


, .



— , Dictionary<T> , :



class HttpHeaders
{
    public string this[string key]
    {
        get => throw new NotImplementedException();
        set => throw new NotImplementedException();
    }
}

class Program
{
    static void Main(string[] args)
    {
        var headers = new HttpHeaders
        {
            ["access-control-allow-origin"] = "*",
            ["cache-control"] = "max-age=315360000, public, immutable"
        };
    }
}




C# 7.0 . :



var point = (5, 7);
// decomposing tuple into separated variables
var (x, y) = point;


:



ValueTuple<int, int> point = new ValueTuple<int, int>(1, 4);
int x = point.Item1;
int y = point.Item2;


:



int x = 5, y = 7;
//switch
(x, y) = (y,x);


:



class Point
{
    public int X { get; }
    public int Y { get; }

    public Point(int x, int y)  => (X, Y) = (x, y);
}


, . , :



  • Deconstruct
  • void
  • out


Point :



class Point
{
    public int X { get; }
    public int Y { get; }

    public Point(int x, int y) => (X, Y) = (x, y);

    public void Deconstruct(out int x, out int y) => (x, y) = (X, Y);
}


:



var point = new Point(2, 4);
var (x, y) = point;


" " :



int x;
int y;
new Point(2, 4).Deconstruct(out x, out y);


:



public static class PointExtensions
{
     public static void Deconstruct(this Point @this, out int x, out int y) => (x, y) = (@this.X, @this.Y);
}


KeyValuePair<TKey, TValue>, :



foreach (var (key, value) in new Dictionary<int, string> { [1] = "val1", [2] = "val2" })
{
    //TODO: Do something
}


KeyValuePair<TKey, TValue>.Deconstruct(TKey, TValue) netstandard2.1. netstandard .



awaitable



C# 5.0 ( Visual Studio 2012) async/await, . , :



void DoSomething()
{
    DoSomethingAsync().ContinueWith((task1) => {
        if (task1.IsCompletedSuccessfully)
        {
            DoSomethingElse1Async(task1.Result).ContinueWith((task2) => {
                if (task2.IsCompletedSuccessfully)
                {
                    DoSomethingElse2Async(task2.Result).ContinueWith((task3) => {
                        //TODO: Do something
                    });
                }
            });
        }
    });
}

private Task<int> DoSomethingAsync() => throw new NotImplementedException();
private Task<int> DoSomethingElse1Async(int i) => throw new NotImplementedException();
private Task<int> DoSomethingElse2Async(int i) => throw new NotImplementedException();


async/await:



async Task DoSomething()
{
    var res1 = await DoSomethingAsync();
    var res2 = await DoSomethingElse1Async(res1);
    await DoSomethingElse2Async(res2);
}


, await Task. , GetAwaiter, :



  • System.Runtime.CompilerServices.INotifyCompletion void OnCompleted(Action continuation)
  • IsCompleted
  • GetResult


await GetAwaiter, TaskAwaiter<TResult> , :



class CustomAwaitable
{
    public CustomAwaiter GetAwaiter() => throw new NotImplementedException();
}

class CustomAwaiter: INotifyCompletion
{
    public void OnCompleted(Action continuation) => throw new NotImplementedException();

    public bool IsCompleted => throw new NotImplementedException();

    public void GetResult() => throw new NotImplementedException();
}


: " await awaitable ?". , Stephen Toub "await anything", .



query expression



C# 3.0 — Language-Integrated Query, LINQ, SQL- . LINQ : SQL- . , . . , . LINQ , SQL- , . . C#, CLR. LINQ IEnumerable, IEnumerable<T> IQuerable<T>, , , query expression. , LINQ, :



class C
{
    public C<T> Cast<T>();
}

class C<T> : C
{
    public C<T> Where(Func<T,bool> predicate);

    public C<U> Select<U>(Func<T,U> selector);

    public C<V> SelectMany<U,V>(Func<T,C<U>> selector, Func<T,U,V> resultSelector);

    public C<V> Join<U,K,V>(C<U> inner, Func<T,K> outerKeySelector, Func<U,K> innerKeySelector, Func<T,U,V> resultSelector);

    public C<V> GroupJoin<U,K,V>(C<U> inner, Func<T,K> outerKeySelector, Func<U,K> innerKeySelector, Func<T,C<U>,V> resultSelector);

    public O<T> OrderBy<K>(Func<T,K> keySelector);

    public O<T> OrderByDescending<K>(Func<T,K> keySelector);

    public C<G<K,T>> GroupBy<K>(Func<T,K> keySelector);

    public C<G<K,E>> GroupBy<K,E>(Func<T,K> keySelector, Func<T,E> elementSelector);
}

class O<T> : C<T>
{
    public O<T> ThenBy<K>(Func<T,K> keySelector);

    public O<T> ThenByDescending<K>(Func<T,K> keySelector);
}

class G<K,T> : C<T>
{
    public K Key { get; }
}


, , LINQ . LINQ . , , Understand monads with LINQ Miłosz Piechocki.





The purpose of this article is not at all to convince you to abuse these syntactic tricks, but to make them clearer. On the other hand, they cannot always be avoided. They were designed to be used, and sometimes they can make your code better. If you are afraid that the resulting code will be incomprehensible to your colleagues, you need to find a way to share your knowledge with them (or at least a link to this article). I’m not sure that this is a complete set of such “magic methods”, so if you know any more, please share in the comments.




All Articles