Mercury/32 v4.90 released

David Harris hat am 23.10.2021 eine neue Version des Mercury/32 Mailserver veröffentlicht.

Version 4.9 is an important major release containing numerous significant changes and fixes.

https://community.pmail.com/index.php?u=/topic/11469/mercury-32-v4-90-released

Nützliche Links

http://www.pmail.com/

C#: Factory methode to create new instance

Angenommen, wir haben ein Objekt AppColor, welches uns beim Erstellen der gewünschten Farben hilft. Wir möchten die Möglichkeit besitzen, die Farben über die Parameter Red, Green, Blue und Alpha zu steuern. Weiter brauchen wir verschiedene Möglichkeiten, um das Objekt zu initialisieren – sprich zu erzeugen. Direkt mit allen Parametern RGBA, nur RGB oder den einzelnen Farben. Dies führt dazu, dass wir eine ganze Reihe an Konstruktoren im Objekt haben werden:

public AppColor(byte red = 0, byte green = 0, byte blue=0, byte alpha=0)
{
    Red = red;
    Green = green;
    Blue = blue;
    Alpha = alpha;
}
public AppColor(byte red, byte green, byte blue)
{
    Red = red;
    Green = green;
    Blue = blue;
    Alpha = 0;
}
public AppColor(byte red)
{
    Red = red;
    Green = 0;
    Blue = 0;
    Alpha = 0;
}
...

Eine viel elegantere Lösung können wir mit einer Fabrikmethode (Factory methode) erstellen. Dazu erstellen wir eine Sub-Klasse mit dem Namen Builder, welche sämtliche Parameter als einzelne Methode enthält und als Rückgabewert wieder sich selber enthält. Zusätzlich wird noch eine Methode Create benötigt, welche dann das eigentliche Objekt erstellt:

public class Builder
{
#region fields
private byte _red;
private byte _green;
private byte _blue;
private byte _alpha;

#endregion

#region methodes
public Builder Red(byte red)
{
    _red = red;
    return this;
}
public Builder Green(byte green)
{
    _green = green;
    return this;
}
public Builder Blue(byte blue)
{
    _blue = blue;
    return this;
}
public Builder Alpha(byte alpha)
{
    _alpha = alpha;
    return this;
} 

public AppColor Create()
{
    return new AppColor(_red, _green, _blue, _alpha);
}
#endregion
}

Welche Vorteile haben wir nun mit diesem Konstrukt? Jetzt haben wir die Möglichkeit, eine x-beliebige Kombination von Parameter zu verwenden, um das Objekt AppColor zu erstellen:

// Einzelne Farbe
AppColor colorRed = new AppColor.Builder().Red(255).Create();

// Gemischte Farbe
AppColor mixColor = new AppColor.Builder().Red(22).Green(25).Create();

// Gemischte Farbe
AppColor mixColor2 = new AppColor.Builder().Green(25).Blue(12).Create();

// Gemischte Farbe
AppColor mixColor3 = new AppColor.Builder().Red(255).Green(255).Blue(0).Alpha(215).Create();

...

Ganze Klasse

C#: Statements as expressions

if-Statement as expression

Normales if-Statement:

public static decimal GetPriceByStatement(decimal price, int quantity, bool isPremiumMember)
{
    decimal discountAmount = 0;

    if(quantity >= 12)
    {
        discountAmount += .1M;
    }
    if (isPremiumMember)
    {
        discountAmount += .20M;
    }

    return price * (1 - discountAmount);
}

Das if-Statement als expression:

public static decimal GetPriceByExpression(decimal price, int quantity, bool isPremiumMember)
{
    decimal discountAmount = (quantity >= 12 ? .1M : 0) 
                + (isPremiumMember ? .2M : 0);
    return price * (1 - discountAmount);
}

swtich-statements as expression

Normales switch-Statement:

public static decimal GetNumberOfAccessLevelStatement(AccessLevels accessLevels)
{
    decimal accessLevelNumber = 0;
    switch (accessLevels)
    {
        case AccessLevels.Low:
            accessLevelNumber =  100501;
            break;
        case AccessLevels.LowExtendSecurity:
            accessLevelNumber = 100511;
            break;
        case AccessLevels.Middle:
            accessLevelNumber = 100601;
            break;
        case AccessLevels.MiddleExtendSecurity:
            accessLevelNumber = 100611;
            break;
        case AccessLevels.High:
            accessLevelNumber = 100701;
            break;
        case AccessLevels.HighExtendSecurity:
            accessLevelNumber = 100711;
            break;
        default:
            accessLevelNumber = 0;
            break;
    }
    return accessLevelNumber;
}

Das switch-Statement als switch-expression:

public static decimal GetNumberOfAccessLevelExpression(AccessLevels accessLevels)
{
    decimal accessLevelNumber = accessLevels switch
    {
        AccessLevels.Low => 100501,
        AccessLevels.LowExtendSecurity => 100511,
        AccessLevels.Middle => 100601,
        AccessLevels.MiddleExtendSecurity => 100611,
        AccessLevels.High => 100701,
        AccessLevels.HighExtendSecurity => 100711,
        _ => 0,
    };
    return accessLevelNumber;
}

C#: Zeichenketten richtig vergleichen

Vergleiche die zwei Namen “ROBONAME” und inputName, aber ignoriere die Gross- und Kleinschreibung.

const string ROBONAME = "iRoby";
string inputName = "IROBY";


if (0 == string.Compare(ROBONAME, inputName, true))
{
    Console.WriteLine($"Die Namen {ROBONAME} und {inputName} sind gleich.");
}

Nützliches

C# reference: https://docs.microsoft.com/en-us/dotnet/api/system.string.compare?view=net-5.0

Best practices for comparing strings in .NET: https://docs.microsoft.com/en-us/dotnet/standard/base-types/best-practices-strings

C#: String-Interpolation

Mit dem Dollar-Zeichen definieren wir, dass es sich um einen “interpolated string” handelt.

Struktur:

{<interpolationExpression>[,<alignment>][:<formatString>]}

Beispiel: String mit einem formatierten Datum

var msg = $"Datum: {DateTime.Today:dd.MM.yyyy}";

Beispiel: String mit einem formatierten Dezimalwert:

var shortPi = $"Pi is {Math.PI:F3}";

Beispiel mit Sonderzeichen und einem “conditional operator”:

Als Ausgabe erhalten wir:

Nützliches

C# reference: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/tokens/interpolated

C#: Null-Forgiving Operator (!)

Null-forgiving oder null-suppression operator

Wenn die Null-Prüfung des Compilers aktiviert wurde (“enabled nullable annotation context”), wird das Ausrufezeichen “!” Verwenden, um bewusst einen Nullwert zu erlauben.

Den “nullable annotation context” aktivieren oder deaktivieren

Null-Prüfung des Compilers ein- ausschalten mit Preprocessor-Direktiven:

#nullable enable
#nullable disable

Die Null-Prüfung kann auch auf der Projektebene in der Projekt-Datei eingeschaltet werden:

Nach dem Aktivieren der Null-Prüfung zeigt der Compiler eine Warnung bei möglichen Nullwerten an:

Sollte ein bestimmter Wert auch Null sein dürfen, haben wir die Möglichkeit, den “Null-Forgiving Operator” zu verwenden:

Nützliches

C# reference: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/null-forgiving

C#: Finde die grösste und kleinste Zahl in einem String

Vorgaben

  • Der String enthält nur gültige Zahlen getrennt durch ein Leerzeichen und mindestens eine Zahl
  • Ausgabe ist ein String mit der grössten und kleinsten Zahl

Beispiele

EingabeAusgabe
“1 2 3 4 5 6 7 8 9 10”“10 1”
“-100 99 45 0 1 354”“354 -100”

Meine erste Lösung

public static string HighAndLow(string numbers)
{
    var sortedList = numbers.Split(" ")
                .Select(x => Convert.ToDecimal(x))
                .OrderByDescending(o => o);
    return $"{sortedList.First()} {sortedList.Last()}";
}

Meine zweite optimierte Lösung

public static string HighAndLow(string numbers)
{
    var sortedList = numbers.Split(" ").Select(int.Parse);
    return $"{sortedList.Max()} {sortedList.Min()}";
}

Unit-Test

public void GetHighAndLowTest()
{
    Assert.AreEqual("354 -100", Snippets.HighAndLow("-100 99 45 0 1 354"));
    Assert.AreEqual("10 1", Snippets.HighAndLow("1 2 3 4 5 6 7 8 9 10"));
    Assert.AreEqual("1 1", Snippets.HighAndLow("1"));
    Assert.AreEqual("3002 -68", Snippets.HighAndLow("-68 3001 3 5 -5 " +
        "42 3002 -1 0 0 -9 4 7 4 -4"));
    Assert.AreEqual("42 -9", Snippets.HighAndLow("8 3 -5 42 -1 0 0 -9 4 " +
        "7 4 -4"));
}

C#: Sortiere die Wörter im String anhand ihrer enthaltenen Zahl

Vorgaben

  • Der String enthält verschiedene Wörter, getrennt durch ein Leerzeichen, welche richtig sortiert einen Satz ergeben
  • Jedes Wort hat an beliebiger Stelle eine Zahl, welche die Position im Satz angibt
  • Wenn der Eingabe-String leer ist, gibt die Funktion einen leeren String zurück
  • Der Eingabe-String enthält nur gültige Werte

Beispiel

EingabeAusgabe
Ap7fel h2abe Ic1h un4d Hung3er 5möchte ein6Ic1h h2abe Hung3er un4d 5möchte ein6 Ap7fel

Meine erste Lösung

public static string OrderWordsByNumber2(string words)
{
    if (string.IsNullOrWhiteSpace(words)) return string.Empty;

    return String.Join(" ", words.Split(" ")
        .Select(x => new { word = x, pos = x.First(v => Char.IsDigit(v)) })
        .OrderBy(x => x.pos)
        .Select(x => x.word));
}

Meine zweite optimierte Lösung

public static string OrderWordsByNumber(string words)
{
    if (string.IsNullOrWhiteSpace(words)) return string.Empty;
    return String.Join(" ", words.Split(" ")
        .OrderBy(x => x.ToList().First(v => Char.IsDigit(v))));        }
}

Unit-Test

[Test]
public void DuplicateCountTest()
{
    Assert.AreEqual("1Das is2t ei3ne spannend4e Aufgabe5",
        Snippets.OrderWordsByNumber("Aufgabe5 1Das ei3ne is2t spannend4e"));
    Assert.AreEqual("Ic1h h2abe Hung3er un4d 5möchte ein6 Ap7fel",
        Snippets.OrderWordsByNumber("Ap7fel h2abe Ic1h un4d Hung3er 5möchte ein6"));
    Assert.AreEqual("", Snippets.OrderWordsByNumber(""));
}

C#: String zu “Camel-Case” konvertieren

Vorgaben

  • Die Funktion entfernt “-“, “_” und ändert den darauf folgenden Buchstaben auf einen Grossbuchstaben
  • Der erste Buchstaben wird nur grossgeschrieben, wenn dieser bereits im Eingabe-String gross ist

Beispiele

calculate_total-pricecalculateTotalPrice
Get_from-weather_apiGetFromWeatherApi

Meine Lösung

public static string ToCamelCase(string str)
{
    return Regex.Replace(str, @"[-_]\w", delegate(Match matches)
        {
            return matches.ToString().Substring(1).ToUpper();
        });
}

Meine optimierte Lösung

Die Regex Anpassung mit zusätzlichen Klammern @”[-_](\w)” – Subexpression ermöglicht es, direkt auf den gesuchten Buchstaben zuzugreifen.

public static string ToCamelCase(string str)
{
    return Regex.Replace(str, @"[-_](\w)", delegate (Match matches)
    {
        return matches.Groups[1].Value.ToUpper();
    });
}

Meine zweite optimierte Lösung

Das “Delegate” kann auch mit “Lambda expressions” geschrieben werden.

Lambda expression

public static string ToCamelCase(string str)
{
    return Regex.Replace(str, @"[-_](\w)", 
        m => m.Groups[1].Value.ToUpper());
}

Unit-Test

public void ToCamelCaseTest()
{
    Assert.AreEqual("calculateTotalPrice", 
        Snippets.ToCamelCase("calculate_total-price"));
    Assert.AreEqual("GetFromWeatherApi", 
        Snippets.ToCamelCase("Get_from-weather_api"));
}

Nützliches

Tool for building and testing regular expressions on the .NET regex engine: http://regexstorm.net/tester

C#: Finde die Anzahl mehrfach vorhandenen alphanumerischer Zeichen oder Zahlen in einem String

Vorgaben

  • Gross- Kleinschreibung spielt keine Rolle
  • String enthält nur alphanumerische Zeichen oder Zahlen

Beispiele

StringRückgabe der Funktion
“abcde”0
“aacDeii”2
“Aaahjkl”1

Meine erste Lösung

public static int DuplicateCount(string str)
{
    var charAmounts = str.GroupBy(x => x.ToString(),
        StringComparer.CurrentCultureIgnoreCase)
        .Select(x => new { Character = x.Key, Count = x.Count() });

    return charAmounts.Where(x => x.Count > 1).Count();
}

Meine zweite verbesserte Lösung

public static int DuplicateCount(string str)
{
    return str.ToLower().GroupBy(x => x)
        .Where(y => y.Count() > 1).Count();
}

Unit-Test

[Test]
public void DuplicateCountTest()
{
    Assert.AreEqual(0, Snippets.DuplicateCount(""));
    Assert.AreEqual(0, Snippets.DuplicateCount("abcde"));
    Assert.AreEqual(2, Snippets.DuplicateCount("abbcdde"));
    Assert.AreEqual(2, Snippets.DuplicateCount("aabBcde"));
    Assert.AreEqual(2, Snippets.DuplicateCount("Schifffahrt"));
    Assert.AreEqual(3, Snippets.DuplicateCount("Affenbrotbaum"));
    Assert.AreEqual(1, Snippets.DuplicateCount("Nuss"));
    Assert.AreEqual(1, Snippets.DuplicateCount("2mal2"));
    Assert.AreEqual(2, Snippets.DuplicateCount("333Fragen22"));
}