Co w trawie piszczy

Fail Fast

Indent Hadouken

public object is_smth(string txt)
{
    if(txt == "hello")
    {
        // Some custom long code 
        // that expands on 50 lines 
        // of code with some other if's
        return smth;
    }
    
    return null; // throw new Exception ();
}
public object is_smth(string txt)
{
    if(txt != "hello")
    {
        return null; // throw new Exception ();    
    }
    
    // Some custom long code 
    // that expands on 50 lines 
    // of code with some other if's   
    return smth;
}

zamieniamy

na

public static int DoSomething(string val)
{
    int multiplier;
    
    if(!int.TryParse(val, out multiplier)) 
    {
        return generate_code(true);
    }

    // do some processing

    return generate_code(false);

    int generate_code(bool isNull) 
    {
        return isNull ? 0 : multiplier * 10;
    }
}

Local Function

dostępne od C# 7.0

public static int DoSomething(string val)
{
    int multiplier;
    
    if(!int.TryParse(val, out multiplier)) 
    {
        return generate_code(true);
    }

    // do some processing

    return generate_code(false);

    static int generate_code(bool isNull) 
    {
        return isNull ? 0 : 10;
    }
}

Static Local Function

dostępne od C# 8.0

public static int DoSomething(string val)
{
    if(!int.TryParse(val, out int multiplier))
    {
        return generate_code(true);
    }

    return generate_code(false);
    
    int generate_code(bool isNull) 
    {
        return isNull ? 0 : multiplier * 10;
    }
}

Out Variables

dostępne od C# 7.0

public static int DoSomething(string val)
{
    if(!int.TryParse(val, out _)) 
    {
        return generate_code(true);
    }

    return generate_code(false);
    
    static int generate_code(bool isNull) 
    {
        return isNull ? 0 : 10;
    }
}

Null check

dostępne od C# 9.0

public static int DoSomething(string val!)
{
    if(!int.TryParse(val, out _)) 
    {
        return generate_code(true);
    }

    return generate_code(false);
    
    static int generate_code(bool isNull) 
    {
        return isNull ? 0 : 10;
    }
}

Explicit is better than implicit

// old C# prior 6

public class Person
{
    public int Age { get; private set; }
    
    public Person(int age)
    {
        Age = age;
    }
}

Read-only auto properties with initialization

dostępne od C# 6.0

// new C# 6 and up

public class Person
{
    // is this the same???
    public int Age { get; } = -1;
    
    public Person(int age)
    {
        Age = age;
    }
}
// old C# prior 6 with expression body changes from C# 7

public class Person
{
    private readonly _age = -1;
    public int Age { get => _age; }
    
    public Person(int age)
    {
        _age = age;
    }
}

Read-only auto properties with initialization

dostępne od C# 6.0

// new C# 6 and up

public class Person
{
    public int Age { get; } = -1;
    
    public Person(int age)
    {
        Age = age;
    }
}

init read-only auto properties

dostępne od C# 9.0

// new C# 6 and up

public class Person
{
    public int Age { get; init; } = -1; // explicit init!
    
    public Person(int age)
    {
        Age = age;
    }
}

var p = new Person(10);
Person pp = new (10);
var ppp = new Person 
{
    Age = 10
};

expression body

dostępne od C# 7.0

public class Person
{
    public string FullName { get; }
 
    // bit too implicit
    public Person(string fullName) => FullName = fullName;
}

record

dostępne od C# 9.0

// old C#
public class Person
{
    public string FullName { get; }
 
    // bit too implicit
    public Person(string fullName) => FullName = fullName;
}

# new C#
data public class Person(string FullName);
data public class Person { string FullName; }

filters/guards on Exception

dostępne od C# 6.0

try 
{

}
catch(HttpRequestException hre)  when (hre.Message.Contains("404"))
{
    // do something for 404
}
catch(HttpRequestException hre)  when (hre.Message.Contains("401"))
{
    // do something for 401
}
catch(Exception ex) 
{

}

in keyword

dostępne od C# 7.2

public class Person
{
    public int Age { get; }
    
    public Person(int age) => Age = age;
    
    public bool IsOlder(in int age) 
    {
    	return Age > age;
    }
}

var age = 18;
var p = new Person(10);
p.IsOlder(in age);

public int SomeAction(in int index, byte[] array) 
{
    var change = 0;
    
    for(; change < 10; change++) 
    {
        array[index + change] = change;
    }

    // will not work
    // index += change;

    return change;
}

touples / destructors

dostępne od C# 7 + 7.1

(string FirstName, string LastName, int Age) person = ("Jan", "Kowalski", 33);
var info = $"{person.FirstName} {person.LastName} is {person.Age} old";

var person = (Name: "Jan", Surname: "Kowalski");
// person.Name

int age = 5;
string name = "Jan";
var pair = (age, name);
// pair.age, pair.name

var point = new Point(3, 4);
(int X, int Y) = point;
(_, int Y) = point;

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

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

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

using declaration

dostępne od C# 8

// old C#
using(var reader = new StringReader(str)) 
{
    var line = reader.ReadLine();
    return line;
}

// new C#
using var reader = new StringReader(str);
var line = reader.ReadLine();
return line;


// or we can even using await if object implements IAsyncDisposable
await using var obj = new DisposableObject(str);
var result = reader.DoAction();
return result;

Clear code is better than concise code

pattern matching

dostępne od C# 7.0

int num = 10;
int? nullNum = null;

var numIsNum = num is int newNum;
var numIsAlwaysSomething = num is var anotherNuNum;
var nullNumIsNull = nullNum is null;
var nullNumIsNotNull = nullNum is {};

if(nullNum is int anotherVar) 
{

}

// we don't need to do if(obj is Test) { var x = obj as Test; }

pattern matching

dostępne od C# 9.0

int num = 10;
int? nullNum = null;

var nullNumIsNull = nullNum is null;

// even more explicit
var nullNumIsNotNull = nullNum is not null;

// than
var nullNumIsNotNullOld = nullNum is {};

pattern matching

dostępne od C# 7.0 + 7.1 (Generic support)

public void Do<T>(T a) 
{
    switch(a) 
    {
        case 0:
            break;
        case IEnumerable<ushort> myVar:
            // do somthing with myVar;
            break;
        case int n when n > 0:
            // do something with n
            break;
        case null:
        default:
            break;
    }
}

pattern matching

dostępne od C# 9.0

public void Do<T>(T a) 
{
    switch(a) 
    {
        case 0:
            break;
        case IEnumerable<ushort> myVar:
            // do somthing with myVar;
            break;
        case int n when n > 0:
            // do something with n
            break;
        case not null:
            // do something
            break;
        case null:
        default:
            break;
    }
}

number literals

dostępne od C# 7.0 + 7.2

// old C#
var i = 100000;
var x = 0xFFBB;
var b = 0b01011100;

// new, clear code over concise code
var i = 100_000;
var x = 0x_FF_BB;
var b = 0b_0101_1100;

null coalescing assignment

dostępne od C# 8.0

// old C#
List<int> list = null;

if(list == null) 
{
    list = new List<int>();
}

int? i = null;
i = i == null ? 10 : i;
i = i == null ? 20 : i;


// new C#
List<int> list = null;

list ??= new List<int>();

int? i = null;

// this might lead to miss understanding
i ??= 10;
i ??= 20;

switch expression

dostępne od C# 8.0

public static string DailyGreeting(DayOfTheWeek day) 
{
    return day switch
    {
        DayOfTheWeek.Monday => "It's the luckiest day of the weekly!",
        DayOfTheWeek.Tuesday => "",
        DayOfTheWeek.Wednesday => "It's hump day",
        DayOfTheWeek.Thursday => "It's almost the weekend!",
        DayOfTheWeek.Friday => "It's the weekend baby!",
        DayOfTheWeek.Saturday => "Party like it's you're on spring break",
        DayOfTheWeek.Sunday => "Lazy day...",
        _ => throw new ArgumentException("invalid enum value", nameof(day))
    };
}

public static decimal ComputeSalaryBooster(Address location, decimal baseSalary) =>
    location switch
    {
        { City: "Warsawa" } => baseSalary * 1.5M,
        { City: "Krakow" } => baseSalary * 1M,
        { City: "Wroclaw" } => baseSalary * 1.1M,
        // other cases removed for brevity...
        _ => 0M
    };

switch expression

dostępne od C# 9.0

public static string DailyGreeting(int day) 
{
    // it might be hard to read for more complex code, but for simple
    / if else if it should work perfectly
    return day switch
    {
        > 1 and < 2 => "It's the luckiest day of the weekly!",
        < 4         => "",
        5           => "It's the weekend baby!",
        <= 7        => "Party like you're under sharp fog shadows",
        _           => throw new ArgumentException("err", nameof(day))
    };
}

touples with pattern matching

dostępne od C# 8

// from MSDN, using tuples
public static string RockPaperScissors(string first, string second)
    => (first, second) switch
    {
        ("rock", "paper") => "rock is covered by paper. Paper wins.",
        ("rock", "scissors") => "rock breaks scissors. Rock wins.",
        ("paper", "rock") => "paper covers rock. Paper wins.",
        ("paper", "scissors") => "paper is cut by scissors. Scissors wins.",
        ("scissors", "rock") => "scissors is broken by rock. Rock wins.",
        ("scissors", "paper") => "scissors cuts paper. Scissors wins.",
        (_, _) => "tie"
    };
    
static Quadrant GetQuadrant(Point point) => point switch
{
    (0, 0) => Quadrant.Origin,
    var (x, y) when x > 0 && y > 0 => Quadrant.One,
    var (x, y) when x < 0 && y > 0 => Quadrant.Two,
    var (x, y) when x < 0 && y < 0 => Quadrant.Three,
    var (x, y) when x > 0 && y < 0 => Quadrant.Four,
    var (_, _) => Quadrant.OnBorder,
    _ => Quadrant.Unknown
};

default literal expression

dostępne od C# 7.1

// old C#
int i = default(int);
public Task SomeAsync(CancelletionToken ct = default(CancelletionToken)) {}

// new C#
int i = default;
public Task SomeAsync(CancelletionToken ct = default) {}

index and range

dostępne od C# 8

var words = new string[]
{
                    // index from start    index from end
    "Siala",        // 0                   ^9
    "baba",         // 1                   ^8
    "mak",          // 2                   ^7
    "nie",          // 3                   ^6
    "wiedziala",    // 4                   ^5
    "jak",          // 5                   ^4
    "a",            // 6                   ^3
    "dziad",        // 7                   ^2
    "wiedzial"      // 8                   ^1
};                  // 9 (or words.Length) ^0

// last word
var last = words[^1];
var previousToLast = words[^2];

// last two words:
var dziadWiedzial = words[^2..^0];

// first four words:
var firstPhrase = words[..4]
// first, second and third word, without forth
Range phrase = 1..4;
var fromRange = words[phrase];

words[^2] = "nie";
words[^1] = "powiedzial";

to wszystko?

nie

nullable reference

dostępne od C# 8

// csproj
// <LangVersion>8.0</LangVersion>
// <Nullable>enable</Nullable>
// 
// #nullable enable
// #nullable restore

// non-nullable reference type
string name;
// nullable reference type
string? nameBis;

// non-nullable reference type
Person person;
// nullable reference type
Person? personBis;

// we know that personBis is not null, so omit comipiler warning
personBis!.Name;

Person p = null;  // warning
Person p = null!;  // ok
Person p = default!;  // ok

async main

dostępne od C# 7.1

namespace TestProduct
{
    public class Program
    {
        static async Task<int> Main () 
        {
            var i = 10;
            var z = static_local();
            
            var t = new Test();
            
            retrun Task.FromResult(1);
            
            static int static_local() => return 1; // c# 8
        }
    }
}

public class Test {}

"main"

dostępne od C# 9

var i = 10;
var z = static_local();

var t = new Test();

retrun Task.FromResult(1);

static int static_local() => return 1;
 

public class Test {}

default interface implementation

dostępne od C# 8

public interface ILogger
{
    void Info(string message);
    
    void Error(string message) => Debug.WriteLine(message);
 
    // New method
    void Warn(string message)
    {
        Debug.WriteLine(message);
    }
}

public class Logger : ILogger
{
    public void Info(string message) => Debug.WriteLine(message);
}

to wszystko?

Nie

  • Tych zmian było dużo, dużo więcej
  • Bardzo dużo tyczyło się słowa kluczowego ref jak i struct
  • Były też flagi dla kompilatora

To czemu o nich nie było?

Gdzie szukać?