LoginSignup
148
111

More than 3 years have passed since last update.

C# 小ネタ:C# 8.0 風の書き方アレコレ

Last updated at Posted at 2020-07-25

筆休めのための、ちょっとした小ネタです。C# 9.0 が今年の秋くらいに出ると思われるのですが、今の最新の C# 8.0 すらキャッチアップできてないのに…という気持ちになりますね。

ということで、完全な機能の網羅は岩永さんのサイトにお任せするとして、ここでは C# 8.0 らしい書き方で書いた方が良さそうなもので、自分的に利用頻度高めかな?と思うものを書いていこうと思います。

因みにここで紹介するものは C# 6 や 7 あたりで追加されたものとかも入ると思います。最近の C# の書き方って感じです。

変数の型の判定方法

あまり引数の型を判定して何か処理するみたいなのが多くあると、ちょっとどうかな?という感じですが、変数の型判定する事はありますよね。

古き良き書き方だと大体以下のような感じですね。

void SomeFunction(object x)
{
  var foo = x as Foo;
  if (foo != null)
  {
    Console.WriteLine("x は Foo 型");
  }
}

前は as で型変換してから、変換がうまくいっているかチェックするという二段構えになっていました。他の方法としては is 演算子で型チェックをしてからキャストする方法もありました。以下のような感じですね。

void SomeFunction(object x)
{
  if (x is Foo)
  {
    var foo = (Foo)x;
    Console.WriteLine("x は Foo 型");
  }
}

因みに、この方法は内部的には is で型をチェックして、さらにキャストするという感じなのでちょっと無駄があるので as の方がいいと言われてました。

最近は is での型判定のところで、そのまま変数を定義できるようになっているので以下のように書くのがスムーズです。

void SomeFunction(object x)
{
  if (x is Foo foo) // foo 変数定義できる!
  {
    Console.WriteLine("x は Foo 型");
  }
}

out 引数での結果の受け取り方

out 引数で結果を受け取るときは、昔は以下のように書いてました。

var input = Console.ReadLine();
int result;
if (int.TryParse(input, out result))
{
  Console.WriteLine($"入力された値を整数に変換できた! ${result}");
}

out 引数に渡すための変数をあらかじめ定義しておいて渡すという感じですね。型の判定のところでも説明したのと同じように out 引数のところで変数の定義ができます。

var input = Console.ReadLine();
if (int.TryParse(input, out var result)) // ここで宣言できる
{
  Console.WriteLine($"入力された値を整数に変換できた! ${result}");
}

プロパティの組み合わせの値に応じて処理を分けたい

例えば X, Y という int 型のプロパティのある Point 型で (0, 0) と (1, 1) と (1, 任意の値) と (任意の値, 1) と (任意の値, 任意の値) で処理がわかれるようなケースを考えてみましょう。

素直に書くとこんな感じですね

var p = new Point { X = 1, Y = 1 };
if (p.X == 0 && p.Y == 0)
{
  // (0, 0)
}
else if (p.X == 1 && p.Y == 1)
{
  // (1, 1)
}
else if (p.X == 1)
{
  // (1, 任意の値)
}
else if (p.Y == 1)
{
  // (任意の値, 1)
}
else
{
  // (任意の値, 任意の値)
}

コンパイル通してないので、間違えてるかもしれませんが素直に書くとこんな感じですよね。最近の C# ではパターンマッチがあるので、こんな感じに書けます。

var p = new Point { X = 10, Y = 1 };
switch (p)
{
  case { X: 0, Y: 0 }:
    // (0, 0)
    break;
  case { X: 1, Y: 1 }:
    // (1, 1)
    break;
  case { X: 1, Y: var y }
    // (1, 任意の値)
    break;
  case { X: var x, Y: 1 }
    // (任意の値, 1)
    break;
  case { X; var x, Y: var y }:
    // (任意の値, 任意の値)
    break;
  default:
    // ここにはこない
    throw new InvalidOperationException();
}

もし上のような switch での分岐の結果行う処理が単一ステートメントの実行結果を返すだけなら switch 式も使えます。

var p = new Point { X = 10, Y = 1 };
var result = p switch 
{
  { X: 0, Y: 0 } => "(0, 0)",
  { X: 1, Y: 1 } => "(1, 1)",
  { X: 1, Y: _ } => "(1, 任意の値)",
  { X: _, Y: 1 } => "(任意の値, 1)",
  { X: _, Y: _ } => "(任意の値, 任意の値)",
}

_ を指定することで値を捨てることができます、_ の代わりに var x のように書くとプロパティの値を変数に入れることができます。

因みに、この { ... } の書き方は型の判定も併せて行いたい場合は型名と合わせて使うこともできます。switch 式に特化した書き方でもないので if 文の中でも使うことができます。

void SomeFunction(object x)
{
  if (x is string { Length: 5 } s)
  {
    // 長さ 5 の文字列
  }
}

複数の値を返したい

タプルを返すようにすると擬似的に複数の戻り値を返すようなメソッドを定義できます。今までは out 引数を使うか、戻り値を表すクラスを定義するとかしないといけなかったのですが、タプルを使うという選択肢が追加されてます。

TryParse をラップして戻り値で bool と int を返すようにしてみようと思います。

(bool ok, int result) ParseInt(string s)
{
  var ok = int.TryParse(s, out var r);
  return (ok, r);
}

戻り値を Task<(bool ok, int result)> のようにすると非同期メソッドでも複数の値を返すことができます。

因みにタプルは以下のように代入したり switch に渡したり色々できるので便利です。

// こんな感じで代入できる
var (ok, result) = ParseInt("100");
Console.WriteLine($"{ok}, {result}"); // true, 100

// switch 式に渡したりも
var x = ParseInt("100") switch
{
  (true, var r) => r, // パースに成功したら結果を返す
  (false, _) => -1, // 失敗時は -1
};

ラムダ式を変数に入れたい

地味にめんどくさかったんですよね。var が使えなくて。

var f = () => Console.WriteLine("Hello world"); // だめ
Action f = () => Console.WriteLine("Hello world"); // OK

引数が多くなってくるとめんどくささが上がります。

こんな時はメソッド内でもメソッドを定義できるようになったので、それを使うと同じようなことができます。

void SomeFunction()
{
  // キャプチャ変数を使わないほうが性能には優しい。static をつけると変数のキャプチャをしないことを明示できる。
  static void f() => Console.WriteLine("Hello world");

  f(); // 呼び出し
}

null のときだけ代入したい

今までは if(x == null) { x = "xxx"; } みたいにするか下の例に書くように、初見殺し的に書くしかなかったのですが ??= 演算子でさくっと出来るようになりました。便利!!

class Hoge
{
    private Command _oldCommand;
    // 初見殺し
    public Command OldCommand =>
        _oldCommand ?? (_oldCommand = new Command(() => Console.WriteLine("Before")));

    private Command _newCommand;
    // いいね
    public Command NewCommand => 
        _newCommand ??= new Command(() => Console.WriteLine("After"));
}

コンソールアプリの Main でも非同期処理したい

前は void Main() みたいに voidint じゃないとだめだったのですが async Task Main() のように async もいけるようになりました。

using System;
using System.Threading.Tasks;

namespace ConsoleApp5
{
    class Program
    {
        static async Task Main()
        {
            Console.WriteLine("Foo");
            await Task.Delay(1000);
            Console.WriteLine("Bar");
        }
    }
}

今までは、以下のようにしてお茶を濁してたのでとても便利です。

using System;
using System.Threading.Tasks;

namespace ConsoleApp5
{
    class Program
    {
        static void Main()
        {
            RunAsync().Wait();
        }

        static async Task RunAsync()
        {
            Console.WriteLine("Foo");
            await Task.Delay(1000);
            Console.WriteLine("Bar");
        }
    }
}

まとめ

ということで、思いついた機能をつらつらと書いていきました。
因みに、iPad でコンパイルせずに書いてるので typo があったり間違ったことを言ってたら教えてください。

ではでは。

148
111
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
148
111