Is there something superfluous?

    Everything will be fast. This performance of Anatoly Levenchuk has been haunting me lately. The success of deep learning in the last year suggests that everything will change very quickly. For too long, screaming wolves, wolves said "artificial intelligence", but he still was not. And so, when he finally comes to us, many people simply don’t realize this, thinking that everything will end with the next victory of the computer in the next intellectual game. Too many people, if not all of humanity, will be left out of progress. And this process is already running. I think that at this moment I will not be very interested in the question that is put in the title of the article. But, while this moment has not yet arrived, I will try to raise this potentially controversial issue.

    Programming for more than 25 years, I found quite a lot of different concepts, I was able to try something, I did not have time yet. Now I'm watching with interest the Go language, which can be attributed to the successors of the “line of Wirth languages” - Algol-Pascal-Modula-Oberon. One of the remarkable features of this chain is that each subsequent language becomes simpler than the previous one , but no less powerful and expressive.

    I think that everyone understands why a simple language is good. But still, I will give these criteria, since they will pop up later:

    • A simple language is faster to learn, so it’s easier to get the necessary developers.
    • Supporting a program in a simpler language is usually simpler (yes, this is an intuitive feeling that needs to be proved, but I will take it now for an axiom).
    • In a simpler language, it is easier to develop the infrastructure surrounding it - transfer to different platforms, create various utilities, generators, parsers, etc.


    Why, then, are complex languages? It's all about expressiveness. If some design allows you to briefly describe the necessary action, then this may well pay off the negative sides of the complexity of the language.

    For a relatively short time of its existence, the C # language has absorbed a significant number of different concepts reflected in its designs. The speed of their addition is sometimes scary. To me, since I'm with C # almost from the very beginning, it's easier. But what about beginners who are just getting started? Sometimes I envy Java programmers, where innovations are introduced into the language much more conservatively.

    What is added to the language - you really cannot even cut it out with an ax. Of course, if you take a language that is widespread in narrow circles, you can afford incompatibility between versions. Some “pranks” of backward incompatibility can be afforded by a language such as Python (when switching from the 2nd to the 3rd version). But not the C # behind which Microsoft stands. It seems to me that if the developers understood that with each new feature the language becomes not only more convenient (in certain cases), but also a little closer to their death from “obesity”, then the comments would be a little less enthusiastic than this is the case in the first branch of responses to the innovations of C # 7 .

    What is described below is just my speculation on whether this is really a useful thing. Of course, this can be a matter of taste and not everyone will agree with me (see spoilers). And in any case, this will remain in C # forever ... Well, until the moment of singularity, at least.

    A list of language features added by version can be found here: C # Features added in versions . I will not touch version 2.0, I will start with 3.0.

    Lyrical memories
    Almost any phenomenon, fact, feature of the language has both positive and negative sides. The assessment we give to this largely depends on our subjective characteristics and the other person can evaluate the same in the opposite way. Not only is this natural, most likely, these estimates will be correct in both cases. Just for different people. In the spoilers, I will try to show examples of such differences.


    C # 3.0


    Implicitly typed local variables


    var i = 5;
    var s = "Hello";
    var d = 1.0;
    var numbers = new int[] {1, 2, 3};
    var orders = new Dictionary();
    

    The notorious var. The introduction of which is now debated in the Java world ( “Var and val in Java?” , “The keyword“ var ”in Java: please, not that” ).

    The code is written once, it reads a lot (banal truth). In many cases, automatic type inference causes additional actions to be taken to understand what type of variable is. So that’s bad. Yes, this is common, for example, for JavaScript programmers, but there is a completely different typing paradigm.

    The annoyance of explicit and complete type assignment is caused by these pieces of code:

    List> scores = seeker.getScores(documentAsCentroid);
    ...
    foreach(Pair score in scores)
    


    And this (Pair) is far from the longest type definition that has to be repeated. And any repetition is really bad (besides being clumsy). But there is a way much better and more expressive. This is what Pascal lacked in Java, and then in C #, because it is a type construct (typedef in C). In C #, I tried to adapt using this, which allows you to write something like this at the beginning of the file:

    using StopWordsTables = System.Collections.Generic.List>;
    


    This construction made it possible to use the StopWordsTables identifier instead of the cumbersome scribble that is on the right. Unfortunately, it is valid only within the limits of one source file ...

    Now if typedef were introduced, this would solve the problem of bulky types without affecting the readability of the code.

    The objection that it would be possible to agree to use var only where the type is easy to deduce (i.e., it is clearly visible in the initializer) will not find support for me for one simple reason. A similar rule has already been introduced by Microsoft in its Code Agreement (I already am silent about my company). Only practically no one is observing this. Var won. People are lazy.

    There is another point - var is very limited. It can be used only in local identifiers. In properties, fields, methods, you also have to write these annoyingly long collection identifiers over and over again, and in case of changing types, repeat editing in all places. With Type / typedef this would all be a thing of the past.

    To develop the topic - if var was introduced, why not bring the idea to its logical conclusion, as is done in Go? In the initializer, instead of “=” write “: =”, which means that the type is displayed automatically. And then you don’t need to write any word in place of type. Even shorter ... By the way, Go also has type, which is very convenient.

    My conclusion is that var in C # was an error. All that was needed was just a typedef.

    In the examples below, I will use var only because it allows us to reduce the size of the example, and its negative aspects do not have time to appear in a couple of lines.

    Object and collection initializers


    var r = new Rectangle {
       P1 = new Point { X = 0, Y = 1 },
       P2 = new Point { X = 2, Y = 3 }
    };
    List digits = new List { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    

    A useful piece that cuts code without sacrificing readability. I approve.

    Auto-Implemented properties


    Now instead
    public Class Point {
       private int x;
       private int y;
       public int X { get { return x; } set { x = value; } }
       public int Y { get { return y; } set { y = value; } }
    }
    

    It became possible to write like this:
    public Class Point {
       public int X { get; set; }
       public int Y { get; set; }
    }
    

    As for the properties at heart, I did not understand, but was it necessary to introduce them? Look, in the same Java and without them it’s quite normal to live using certain naming conventions in methods. But since they have already introduced, such a simplification of their description is quite convenient (without impairing readability) in many cases.

    Anonymous types


    var p1 = new { Name = "Lawnmower", Price = 495.00 };
    var p2 = new { Name = "Shovel", Price = 26.95 };
    p1 = p2;
    

    This language option has never been useful to me. Although no, 1 time after all it was necessary, I remembered. I would not enter. Although those examples that I saw in the textbook seem to be logical. In general, it’s possible and useful, just not in my scripts (I prefer to mess with algorithms, and not with databases and JSON, although, it happens differently).

    Extension methods


    namespace Acme.Utilities
    {
       public static class Extensions
       {
          public static int ToInt32(this string s) {
             return Int32.Parse(s);
          }
          public static T[] Slice(this T[] source, int index, int count) {
             if (index < 0 || count < 0 || source.Length – index < count)
                throw new ArgumentException();
             T[] result = new T[count];
             Array.Copy(source, index, result, 0, count);
             return result;
          }
       }
    }
    


    using Acme.Utilities;
    ...
    string s = "1234";
    int i = s.ToInt32();               // Same as Extensions.ToInt32(s)
    int[] digits = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    int[] a = digits.Slice(4, 3);      // Same as Extensions.Slice(digits, 4, 3)
    

    Very handy thing. At times, OOP theorists scolded her, but without it it would be inconvenient (cumbersome) to do many things.

    Query expressions


    He is LINQ. This item evokes so mixed feelings! Well, about like a fly in the ointment in a barrel of something good. Without a doubt, LINQ was one of the most truly cool features of the language. But why did you need to implement this in two ways? I'm talking about the so-called human-readable syntax (NPV), which imitated SQL queries, as I understand it.

    string[] people = new [] { "Tom", "Dick", "Harry" };
    // ЧПС или же синтаксис запросов
    var filteredPeople = from p in people where p.Length > 3 select p; 
    // функциональный стиль или лямбда синтаксис
    var filteredPeople = people.Where (p => p.Length > 3); 
    


    As a result, we have:

    • NPV does not correspond directly to SQL, so knowledge of SQL is not enough to write the corresponding query. There are some peculiarities.
    • The same thing (with minor and rare exceptions, functional and NPV styles are equivalent) can be written in two ways.
    • The programmer should learn both options, as they can both appear in the code.
    • The NPV style contrasts sharply with the rest of the code, looking something alien.


    WPF bindings evoke similar feelings in terms of style foreignness to me. They are microscript constructs written in their own language inside XML. As a result, everything looks bulky and ugly. I don’t know how it would be possible to make it more beautiful - can it create a specialized markup language, and not put everything in XML? But I was distracted. In general, I confess - over the past few years I have not written a single expression in the NPV, while at the same time I have no need for it. Only a little edited others.
    In general, LINQ is a very, very necessary thing, to which they added a NPV weight very vainly.

    Lambda expressions


    x => x + 1                     // Implicitly typed, expression body
    x => { return x + 1; }         // Implicitly typed, statement body
    (int x) => x + 1               // Explicitly typed, expression body
    (int x) => { return x + 1; }   // Explicitly typed, statement body
    (x, y) => x * y               // Multiple parameters
    () => Console.WriteLine()      // No parameters
    

    It was a great acquisition that brought in C # elements of a functional style and significantly improved the expressiveness of short code fragments passed as arguments. That's only when lambdas begin to occupy ten or more lines, it becomes very difficult to read the code. It is important to stop in time and in this case switch to methods again.

    Expression trees


    It is hardly worth considering this feature separately from LINQ and Lambda.

    Partial methods


    A good way to separate auto-generated and manual code. I am for.

    Venya, he's Ben
    Apart from the MK-61 programmable calculator , the first meeting I had with computers was in the Criminal Procedure Code, in high school. Most of the students stayed out of classes, carefully writing down what teachers read in a notebook, spent some time on implementing algorithms like bubble sorting or binary search on Fokal . But 4 people very quickly departed from the main program and we sat at home TVs (which served us as monitors) after classes (and, a little later during classes), until the stop, realizing our ideas, until the teachers began to beg to “let go” them home .

    Focal - it was a quiet horror, after which BASIC seemed to be an exemplary language, but another language of "high level" on BC 0010 зашито не было. С другой стороны, система команд процессора К1801ВМ1 отличалась удобной структурой, позволявшей относительно легко программировать прямо в кодах, вводя команды в восьмеричной системе (16 бит). Почему восьмеричная? 8 регистров, 8 способов адресации. Именно поэтому было удобно использовать именно такой метод и ассемблер/дизассемблер для небольших программ был не нужен. Немножко неудобно было только вычислять смещения, когда программа предварительно писалась в тетрадке.

    Потом был университет, МС 1502. Хоть это уже и был IBM-PC совместимый компьютер, но поначалу здесь не было дисководов, MS DOS, ассемблеров. Работали прямо из интерпретатора бейсика, который был зашит в биосе.

    И вот тут уже мне стало очевидно, что все люди разные. Был в this gang to our group of Benjamin. He always astounded me by the fact that in his programs I had never noticed errors. Everything he wrote worked the first time. On the MS 1502, the command system (x86) was much less logical (this is my personal opinion) than on the BC 0010 ( PDP-11 ). Therefore, here I stopped writing small inserts directly in the codes. Honestly, I moved away from assembler altogether. But Benjamin continued to compile directly in his head, to write code in the DATA operator, which were then executed directly from basic programs. And everything continued to work the first time! He often didn’t even drop a written program on a tape for several hours — and there was a great risk of freezing in case of an error in the code. In this case, only a reboot helped.

    And so, the day came when Venik (as we sometimes called him), the program did not work. He was amazed, surprised, annoyed and much more ( there is no analogy with the joke about the Russian teacher here - Venya did without a particularly lively part of the Great Russian language). Although I, for example, secretly gloated a bit - after all, you can never make a mistake. But, I was put to shame! After much clarification, it turned out that the problem was an error when describing the operation of one of the processor commands - some flag was set there, it was written that it should be reset (I don’t remember exactly - 25 years have passed).

    So if someone says that he does not need typing, unit tests, he always writes error-free programs - I will believe. I saw such a person (his traces were lost after moving to the United States and starting work at Microsoft). But I and many other people are not like that.


    C # 4.0


    Dynamic binding


    Potentially useful example - instead
    var doc = new XmlDocument("/path/to/file.xml");
    var baz = doc.GetElement("foo").GetElement("qux");
    

    can write
    dynamic doc = new XmlDocument("/path/to/file.xml");
    var baz = doc.foo.qux;
    

    Despite what looks good, I would not recommend such use. The dynamic type is a very dangerous thing, because all type control is lost. From smaller dirty tricks - hints in the editor cease to work. However, in certain scenarios, it is useful. For example, with it, I did plug-in loading myself (more precisely, using code from them). Due to the fact that method calls are cached here, it turns out efficiently and you do not need to block it yourself. And as for security - otherwise I would still have to work through reflection, so in this case the security would not be greater. But the code would be more complicated and confusing. So I approve of the cautious use of speakers in a limited number of scenarios. Of course, they were introduced more with an eye on scripting languages. Well, it’s necessary, it’s necessary.

    Named and optional arguments


    class Car {
      public void Accelerate(
        double speed, int? gear = null, 
        bool inReverse = false) { 
        /* ... */ 
      }
    }
    

    Car myCar = new Car();
    myCar.Accelerate(55);
    

    The number of overloaded methods is reduced - the code becomes simpler and more reliable (fewer opportunities to make a copy-paste error, less work when refactoring). I approve.

    Generic co- and contravariance


    A completely logical refinement of the behavior of the language. The syntax does not introduce any particular complexity and can be considered by beginners later. I approve.

    Embedded interop types ("NoPIA")


    This is one of the features about which I have nothing special to say, based on my practice - I just read what it is. I didn’t need it, but COM apparently will not die for a long time and those who (for example) work with MS Office will need it for a long time.

    C # 5.0


    Asynchronous methods


    public async Task ReadFirstBytesAsync(string filePath1, string filePath2)
    {
        using (FileStream fs1 = new FileStream(filePath1, FileMode.Open))
        using (FileStream fs2 = new FileStream(filePath2, FileMode.Open))
        {
            await fs1.ReadAsync(new byte[1], 0, 1); // 1
            await fs2.ReadAsync(new byte[1], 0, 1); // 2
        }
    }
    

    Very comfortable design. Unfortunately, some practical limitations arose during the practical implementation - implementation details flowed as limitations ( Async / await in C #: pitfalls ). Some of them were removed in the next versions (Await in catch / finally blocks) of the language or libraries (akka.net at first did not allow mixing its own model of asynchronous execution with the feature in question, but then it was fixed). It might make sense to consider some other patterns of parallel interaction - such as goroutin. But here is the choice for the architects of the language. In general, I approve.

    Caller info attributes


    public void DoProcessing()
    {
        TraceMessage("Something happened.");
    }
    public void TraceMessage(string message,
            [System.Runtime.CompilerServices.CallerMemberName] string memberName = "",
            [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "",
            [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
    {
        System.Diagnostics.Trace.WriteLine("message: " + message);
        System.Diagnostics.Trace.WriteLine("member name: " + memberName);
        System.Diagnostics.Trace.WriteLine("source file path: " + sourceFilePath);
        System.Diagnostics.Trace.WriteLine("source line number: " + sourceLineNumber);
    }
    // Sample Output:
    //  message: Something happened.
    //  member name: DoProcessing
    //  source file path: c:\Users\username\Documents\Visual Studio 2012\Projects\CallerInfoCS\CallerInfoCS\Form1.cs
    //  source line number: 31
    

    A small syntactic sugar, which does not burden the language, but allows in certain scenarios to reduce the amount of copy-paste and code. I approve.

    C # 6.0


    Compiler-as-a-service (Roslyn)


    I will skip this point (despite the general importance). I would refer it more to infrastructure, and not directly to the language.

    Import of static type members into namespace


    using static System.Console;
    using static System.Math;
    using static System.DayOfWeek;
    class Program
    {
        static void Main()
        {
            WriteLine(Sqrt(3*3 + 4*4)); 
            WriteLine(Friday - Monday); 
        }
    }
    

    At first, this feature seemed useful to me. But having tried it in practice, I have to admit that I was mistaken. Code readability is deteriorating - methods and members of a static class begin to interfere with the methods of the current class. And even the input has become slower, although it seems that the number of identifiers has decreased by one. But due to the fact that now in the prompt from Intellisense there are more options, more clicks need to be done. In general, this feature, from my point of view, is a mistake.

    Exception filters


    try { … }
    catch (MyException e) when (myfilter(e))
    {
        …
    }
    

    I have not tried it yet. Therefore, there is a temptation to call a feature useless, but maybe my scripts just do not suit it very much? Maybe someone will tell you in what cases and how often it is really good?

    Await in catch / finally blocks


    I do not consider this an independent feature - rather a correction of previous problems.

    Auto property initializers


    public class Customer
    {
        public string First { get; set; } = "Jane";
        public string Last { get; set; } = "Doe";
    }
    

    A logical and convenient addition to car properties. The code becomes cleaner, which means I approve.

    Default values ​​for getter-only properties


    public class Customer
    {
        public string First { get; } = "Jane";
        public string Last { get; } = "Doe";
    }
    

    Similar to the previous paragraph.

    Expression-bodied members


    public string Name => First + " " + Last;
    public Customer this[long id] => store.LookupCustomer(id); 
    

    There is no great practice yet, but it looks good. You will still need to go through your code and see where you can apply. The main thing here as with lambdas is not to overdo it and not to make expressions on the split screen.

    Null propagator (succinct null checking)


    public static string Truncate(string value, int length)
    {          
      return value?.Substring(0, Math.Min(value.Length, length));
    }
    

    A long-overdue thing. I approve. Although, in practice, it turned out that it is not applied as often as expected before.

    String Interpolation


    ABOUT! This is what I was expecting a long time ago, and that instantly took root in my code. I always tried to write identifiers in the context of a string like this:

    “Total lines: “ + totalLines + ”, total words: “ + totalWords + ”.”;
    


    Sometimes they asked me, but do I know about formatting strings? Yes, I know, but there are 2 big problems:

    • The expression is far away from where it is inserted. This makes it difficult to read the code.
    • A string with formatting literals is actually a microscript that runs at run-time. Accordingly, the entire system of typing, checking the conformity of C # parameters flies to tartarara.

    This also leads to the fact that in the methods of Format (...) a large number of errors are allowed during refactoring.
    Therefore, I used such a little cumbersome spelling. But, finally, I waited for such a gift from C # :) I unequivocally approve with all the limbs!

    nameof operator


    if (x == null) throw new ArgumentNullException(nameof(x));
    WriteLine(nameof(person.Address.ZipCode)); // prints "ZipCode"
    

    Similar to “Caller info attributes”. I approve.

    Dictionary initializer


    var numbers = new Dictionary {
        [7] = "seven",
        [9] = "nine",
        [13] = "thirteen"
    };
    

    Another innovation that makes it easier to work with code that is often typed using copy-paste and is prone to errors from inattention. Any opportunity to make it cleaner and easier to read will lead to pluses in the karma of the language architect. Plus sign.

    Anti-ben
    В предыдущем спойлере я описал идеального программиста, которого встречал в реальности. Сам же я во многих случаях являюсь полной противоположностью. Я склонен к опечаткам, у меня очень малая сверхоперативная память. Именно поэтому, запуск нового кода всегда для меня начинался с отладки. Постепенно сложились определенные приемы и приемчики, которые позволяли уменьшить количество ошибок. Код дробился до маленьких фрагментов-функций, которые собирались в большие. Это делалось даже в том случае, если обращение ко вложенной функции делалось только один раз. Кстати, венгерская нотация для элементарных типов также уменьшила количество логических ошибок — они становятся виднее зрительно. Также, активно использую саджесты в IDE — они позволяют снизить количество элементарных описок практически до нуля.

    И вот вышла Java. (Это было еще прошлое тысячелетие)

    Прочитал книжку, вдохновился идеей реализации простого http-сервера (просто отдавал статические файлы) и по образу и подобию примера из книги написал свой код с небольшими вариациями. Код именно писал, а не копировал фрагменты. Относительно долго вылавливал всякие неточности, связанные с тем, что язык новый, просто свои обычные опечатки. Наконец, код откомпилировался. Я его запустил, приготовившись к этапу отлова следующих багов. Но программа заработала. И заработала так, как я хотел.

    To say that I was surprised is to say nothing. But it was after this incident that I realized that for me the rigor of the language (and also automatic garbage collection - this was my first language with this feature) turns into the opportunity to work productively on serious projects. I believe that many people don’t like static typing, the need to explicitly describe types, and much more that are related to strict languages. And I believe that it really bothers them. But their audience is Ben, but not me.


    C # 7.0 proposals


    I have not used these functions yet - I usually sit on the release version of sharpe, sometimes I have to go down a little lower. Therefore, I will give purely speculative arguments. The list is based on the article “ C # 7 Innovations ”, and not according to the data from Wikipedia.

    Binary literals


    int x = 0b1010000; 
    int SeventyFive = 0B10_01011;
    

    Innovation looks harmless, not greatly complicating the language, but for those who need to work with bits - conveniently. I did not understand the phrase “You can separate zeros with an arbitrary number of underscores” - why only zeros?

    Local functions


    public void Foo(int z)
    {
        void Init()
        {
            Boo = z;
        }
        Init();
    }
    

    When I just switched to C # from Object Pascal (Delphi), I really lacked local functions as a way to structure my code. Simply putting pieces of code in private methods led to classes with more methods at the same level. This happened until I realized that in C # you need to use another method for this - object decomposition. After that, I often began to make relatively cumbersome code in my inner class with my methods. Upon reaching a certain level of complexity, this class could be divided into several related classes, which were placed in a separate folder and namespace. This allowed us to introduce the hierarchy into the code that in ancient times was provided by Pascal's local functions and procedures.

    Thus, my opinion has now changed - do not give another way to structure the code. This complicates the language, complicates the reading (the external method becomes large, so it is difficult to cover it with a look from the beginning to the end), but there are no fundamental advantages.

    Tuples


    I have not yet understood the necessity of this feature, in what situations it will be more useful than returning a class / structure or using out arguments. Therefore, for me it is rather a negative contribution to the language.

    Pattern matching, conditions in switch


    // type pattern
    public void Foo(object item)
    {
        if (item is string s)
        {
            WriteLine(s.Length);
        }
    }
    // Var Pattern
    public void Foo(object item)
    {
        if(item is var x)
        {
            WriteLine(item == x); // prints true
        }
    }
    // Wildcard Pattern
    public void Foo(object item)
    {
        if(item is *)
        {
            WriteLine("Hi there"); //will be executed
        }
    }
    

    A more complete example can be found here .
    It looks good, but you need to see how useful it will be in practice. There is an intuitive suspicion that the complexity of the language will not pay off with the number of cases where this feature will be useful. So for now, I'm wary.

    Ref locals and ref returns


    static void Main()
    {
        var arr = new[] { 1, 2, 3, 4 };
        ref int Get(int[] array, int index)=> ref array[index]; 
        ref int item = ref Get(arr, 1);
        WriteLine(item);
        item = 10;
        WriteLine(arr[1]);
        ReadLine();
    }
    // Will print
    // 2
    // 10
    

    A simple and intuitive extension for working with links. But the real need for it is not yet clear.

    I do not see the items “Records” and “Creating immutable objects” described in the article on Haber now in current offers for the 7th version of C #, therefore I will not evaluate them.

    So what is the bottom line?


    From my point of view, C # has made money (I’m not touching the 7th version yet, I will mourn after the fact) such extra features :

    1. Human-readable LINQ syntax. It would be enough to dwell on the fluent-style.
    2. Anonymous types.
    3. Var. This feature, limited by local variables, prevented the introduction of a normal type definition, while at the same time significantly degrading the readability of the code.
    4. Importing statics - degrades code readability.


    Which was especially useful :

    1. LINQ (without NPV).
    2. Lambdas
    3. Gradual simplification of initialization and description of objects, various data structures, properties.
    4. Named and default options.
    5. Async / await.
    6. String interpolation.

    Also popular now: