やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

C#チュートリアル(6.0新機能)

 C#6.0で追加された新たな機能について学習する。

成果物

情報源

一覧

プロジェクト作成

dotnet new console -o cs6
cd cs6

Program.cs

using System;

namespace cs6
{
    class Program
    {
        static void Main(string[] args)
        {
            new CS6_0().Run();
            new CS6_1().Run();
            new CS6_2().Run();
            new CS6_3().Run();
            new CS6_4().Run();
            new CS6_5().Run();
            new CS6_6().Run();
            new CS6_7().Run();
            new CS6_8().Run();
        }
    }
}

自動プロパティ(読取専用)

using System;

namespace cs6
{
    class CS6_0
    {
        public void Run()
        {
            var p = new Person("Bill", "Wagner");
            Console.WriteLine("The name, in all caps: " + p.AllCaps());
            Console.WriteLine("The name: " + p);
        }
    }
    public class Person
    {
        public string FirstName { get; private set; }
        public string LastName { get; private set; }

        public Person(string first, string last)
        {
            FirstName = first;
            LastName = last;
        }

        public override string ToString()
        {
            return FirstName + " " + LastName;
        }

        public string AllCaps()
        {
            FirstName = FirstName.ToUpper();
            LastName = LastName.ToUpper();
            return ToString();
        }
    }
}
$ dotnet run
The name, in all caps: BILL WAGNER
The name: BILL WAGNER
  • プロパティのsetprivateアクセス修飾子を付与することで読取専用プロパティが作れるようになった

 もうプロパティ用の変数を宣言する必要はない。

自動プロパティ(初期化)

 CS_0.csの対象箇所を以下のように追加・変更する。

CS_1.cs

public string MiddleName { get; } = "";

public Person(string first, string middle, string last)
{
    FirstName = first;
    MiddleName = middle;
    LastName = last;
}

 プロパティ宣言箇所で= ""のように値を代入できる構文が使えるようになった。

式形式メンバ

 CS_1.csの対象箇所を以下のように追加・変更する。

CS_2.cs

public override string ToString() => FirstName + " " + LastName;

 ラムダ式をメンバとして宣言できるようになった。以前までは以下のように関数としてしか宣言できなかった。

public override string ToString()
{
    return FirstName + " " + LastName;
}

単一クラスのインポート

using static System.Console;
using System;
using static System.Console;

namespace cs6
{
    class CS6_3
    {
        public void Run()
        {
            System.Console.WriteLine("CS6_3");
            Console.WriteLine("CS6_3");
            WriteLine("CS6_3"); // 単一クラスのインポートでここまで省略できるようになった。
        }
    }
}

 略記用っぽい。インポートはSystem全体。たとえConsoleしか使っていなくとも。

向上した文字列形式

public override string ToString() => $"{FirstName} {LastName}";

 ようするに$"{変数名}"のような文字列補完のこと。標準出力だけでなく文字列生成としても使えたらしい。

var p = new Person("Bill", "Wagner");
WriteLine($"The name, in all caps: {p.AllCaps()}");
WriteLine($"The name is: {p}");

 :以降に書式を指定することもできる。

using System.Linq;

var phrase = "the quick brown fox jumps over the lazy dog";
var wordLength = from word in phrase.Split(' ') select word.Length;
var average = wordLength.Average();
WriteLine(average);
WriteLine($"The average word length is: {wordLength.Average()}");
WriteLine($"The average word length is: {wordLength.Average():F2}");

nullチェック

 snullだと例外が発生する。

string s = null;
Console.WriteLine(s.Length); // NullReferenceException 

 それを回避するためにifでnullチェックする。面倒!

string s = null;
if (null != s) Console.WriteLine(s.Length);

 ?nullを返すようにできる。

string s = null;
Console.WriteLine(s?.Length); // 左オペランドがnullなら?はnullを返す

 ?はメソッドの末尾にも付与できる。

bool hasMore = s?.ToCharArray()?.GetEnumerator()?.MoveNext();

 ??(null合体演算子)でnull時の値を返す。

bool hasMore = s?.ToCharArray()?.GetEnumerator()?.MoveNext() ?? false;
Console.WriteLine(hasMore);

 ??は以下のような三項演算子の糖衣構文みたいなもの。

string s2 = (s == null) ? "Default" : s;

 三項演算子if文にすると以下。つまり??は以下をs2 = s ?? "Default"と表記できる。

if (s == null) s2 = "Default";
else s2 = s;
  • ?NullReferenceException例外を発生させずnullを返す
  • ??null時の初期値を設定するときの糖衣構文として重宝する

 ?, ??は10億ドルの損失であるnullへの負荷を軽減するための構文である。

例外フィルタ

 catchwhen句を追加することでフィルタリングする。

catch (Exception e) when (LogException(e)) {}
using System;

namespace cs6
{
    class CS6_6
    {
        public void Run()
        {
            try 
            {
                string s = null;
                Console.WriteLine(s.Length);

            } catch (Exception e) when (LogException(e)) {}
            Console.WriteLine("Exception must have been handled");
        }
        private bool LogException(Exception e)
        {
            Console.WriteLine($"\tIn the log routine. Caught {e.GetType()}");
            Console.WriteLine($"\tMessage: {e.Message}");
            return false;
        }
    }
}

nameof

 nameofは変数や型の名前を文字列として返す。

Console.WriteLine(nameof(System.String)); // String
int j = 5;
Console.WriteLine(nameof(j)); // j
List<string> names = new List<string>();
Console.WriteLine(nameof(names)); // names

オブジェクト初期化構文

 オブジェクト初期化子の構文で、プロパティとフィールドだけでなく "インデクサー" の初期化がサポートされるようになった。

var messages = 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."
};
Console.WriteLine(messages[302]);

 コレクションクラスのAddメソッドは拡張メソッドでもかまわなくなった。

public class Path : IEnumerable<Point3D>
{
    private List<Point3D> points = new List<Point3D>();
    public IEnumerator<Point3D> GetEnumerator() => points.GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator() => points.GetEnumerator();

    public void Add(Point3D pt) => points.Add(pt);
}

public static class Extensions
{
    public static void Add(this Path path, double x, double y, double z) => path.Add(new Point3D(x, y, z));
}

 だが上記はコンパイルエラーになる。

CS6_8.cs(25,38): error CS0246: 型または名前空間の名前 'Point3D' が見つかりませんでした (using ディレクティブまたはアセンブリ参照が指定されていることを確認してください)[/tmp/work/CSharp.6.0.20191018093610/src/cs6/cs6.csproj]

 ググって名前空間を見つけたが、やはりコンパイルエラー。

名前空間: System.Windows.Media.Media3D
Assembly: PresentationCore.dll
using System.Windows.Media.Media3D;
CS6_8.cs(25,38): error CS0246: 型または名前空間の名前 'Point3D' が見つかりませんでした (using ディレクティブまたはアセンブリ参照が指定されていることを確認してください)[/tmp/work/CSharp.6.0.20191018093610/src/cs6/cs6.csproj]

 最後の最後でドキュメントが説明不足。アセンブリ参照の仕方はまだ説明を受けていない。どうやるの?

$ dotnet --help
...
  add               .NET プロジェクトにパッケージまたは参照を追加します。
...
使用法: dotnet add <PROJECT> package [options] <PACKAGE_NAME>

引数:
  <PROJECT>        操作するプロジェクト ファイル。ファイルを指定しない場合、コマンドによって現在のディレクトリから検索されます。
  <PACKAGE_NAME>   追加するパッケージ参照。

オプション:
  -h, --help                          コマンド ラインのヘルプを表示します。
  -v, --version <VERSION>             追加するパッケージのバージョン。
  -f, --framework <FRAMEWORK>         特定のフレームワークを対象とする場合にのみ参照を追加します。
  -n, --no-restore                    復元のプレビューや互換性チェックを行わずに参照を追加します。
  -s, --source <SOURCE>               復元中に使用する NuGet パッケージ ソース。
  --package-directory <PACKAGE_DIR>   パッケージの復元先のディレクトリ。
  --interactive                       コマンドを停止して、ユーザーの入力またはアクション (認証の完了など) を待機できるようにします。
$ dotnet add package PresentationCore.dll
  Writing /tmp/tmpOOPJKF.tmp
info : パッケージ 'PresentationCore.dll' の PackageReference をプロジェクト '/tmp/work/CSharp.Classes.20191018082123/src/classes/classes.csproj' に追加しています。
info : /tmp/work/CSharp.Classes.20191018082123/src/classes/classes.csproj のパッケージを復元しています...
info :   GET https://api.nuget.org/v3-flatcontainer/presentationcore.dll/index.json
info :   NotFound https://api.nuget.org/v3-flatcontainer/presentationcore.dll/index.json 785 ミリ秒
error: パッケージ PresentationCore.dll が見つかりません。ソース nuget.org には、この ID のパッケージが存在しません。
error: パッケージ 'PresentationCore.dll' はプロジェクト '/tmp/work/CSharp.Classes.20191018082123/src/classes/classes.csproj' の 'all' フレームワークとの互換性がありません。

 PresentationCore.dll が見つかりません。ということで動作させられなかった。

 原因はおそらく、PresentationCore.dllはグラフィクス関係APIであり、.NETにおいてグラフィクスAPIをサポートしているのはWindowsのみだからだと思われる。Linuxでは使えない……。

 環境依存なコードをチュートリアルに使うな! Linuxもサポートしてくれ! Windows支配反対!

所感

 最後にモヤモヤさせられて不完全燃焼。Linuxでも使えるとか嘘やんって思ってしまう。

対象環境

$ uname -a
Linux raspberrypi 4.19.42-v7+ #1218 SMP Tue May 14 00:48:17 BST 2019 armv7l GNU/Linux