Quantcast
Channel: ++C++; // 未確認飛行 C ブログ
Viewing all 482 articles
Browse latest View live

小ネタ オブジェクト初期化子

$
0
0

今日の小ネタは、オブジェクト初期化子について、意外と知られてないらしい話。

問い

唐突ですが問題です。以下の3つのコードはそれぞれどういう意味でしょう。

var x = new Line
{
    A = new Point { X = 1, Y = 2 },
    B = new Point { X = 3, Y = 4 },
};
var x = new Line
{
    A = { X = 1, Y = 2 },
    B = { X = 3, Y = 4 },
};
var x = new Line
{
    A = new { X = 1, Y = 2 },
    B = new { X = 3, Y = 4 },
};

ついでに、将来的に認められるようになるかもしれないパターンをもう1つ。

var x = new Line
{
    A = new() { X = 1, Y = 2 },
    B = new() { X = 3, Y = 4 },
};

答え合わせの前に

答えを説明する前に、コード中に出ていた2つの型、PointLineについて。

まず、Pointの方は、どのパターンであっても以下のような感じである必要があります。

class Point
{
    public int X { get; set; }
    public int Y { get; set; }
}

構造体でもいいんですが、その場合、Line側に参照戻り値が必要になります。

Lineの方は、2パターンあります。 1つは、プロパティが書き換え可能なもの。

class Line
{
    public Point A { get; set; }
    public Point B { get; set; }
}

もう1つは、getのみのプロパティに対して、コンストラクター、もしくは、プロパティ初期化子で初期値を与えているものです。

class Line
{
    public Point A { get; } = new Point();
    public Point B { get; } = new Point();
}

答え

パターン1

パターン1のやつは、一番シンプルというか、多くの方がこれのつもりでオブジェクト初期化子を使っているのではないかと思います。

public static void Q()
{
    var x = new Line
    {
        A = new Point { X = 1, Y = 2 },
        B = new Point { X = 3, Y = 4 },
    };
}

public static void A()
{
    var x = new Line();
    x.A = new Point { X = 1, Y = 2 };
    x.B = new Point { X = 3, Y = 4 };
}

展開結果を見ての通り、x.Ax.Bに対する代入が発生するので、A, B は set アクセサーを持つ必要があります。

その結果、書き換え可能な方の Line 実装に対してならこの構文を使えますが、 getのみの方の Line 実装には使えません。

パターン2

意外と知られてないのはこいつですね。

public static void Q()
{
    var x = new Line
    {
        A = { X = 1, Y = 2 },
        B = { X = 3, Y = 4 },
    };
}

public static void A()
{
    var x = new Line();
    x.A.X = 1;
    x.A.Y = 2;
    x.B.X = 3;
    x.B.Y = 4;
}

オブジェクト初期化子は再帰的に書けます。 その場合、この例の x.A.X というように、全部展開されて、そこに代入が行われます。

ここで注意が必要なのは、x.A の初期化は外からは行われないということです。 もしも、Line のコンストラクター内で A を初期化していなければ、当然のようにぬるぽります。

つまり、getのみの方の Line に対しても使える代わりに、 書き換え可能な方の Line 実装みたいにコンストラクター内での初期化をしていないものに対してこの書き方を使うと実行時エラーを起こします。

パターン3

並べられると、似たようなもので全然違う結果になるので気持ち悪くなりますが、 まあ、個別によく見るとそんなに不思議なものではないと思います。

パターン3は、単に匿名型を代入しているだけ。

public static void Q()
{
    // 実はコンパイル エラー
    var x = new Line
    {
        A = new { X = 1, Y = 2 },
        B = new { X = 3, Y = 4 },
    };
}

public static void A()
{
    var x = new Line();
    // この new { } は匿名型。
    // A, B は Point 型なので、匿名型だと型があってない。
    // つまり、コンパイル エラー: 匿名型を暗黙的に Point に変換できません
    x.A = new { X = 1, Y = 2 };
    x.B = new { X = 3, Y = 4 };
}

C#だと、コンパイル時にエラーなことがわかるんでそんなに問題はないと思うんですが。 もしも実行してみないとこの差がわからないとか言われたらちょっと殺意を覚えますね…

パターン4

パターン4は将来の話。今現在はコンパイル エラーになります。 どういう構文が追加されそうかというと、左辺からの型推論です。

public static void Q()
{
    var x = new Line
    {
        A = new() { X = 1, Y = 2 },
        B = new() { X = 3, Y = 4 },
    };
}

public static void A()
{
    var x = new Line();
    // new() って書き方で、左辺から型推論してくれる構文が入りそう。
    // この場合、A, B が Point なので、new () は new Point() の意味。
    x.A = new Point { X = 1, Y = 2 };
    x.B = new Point { X = 3, Y = 4 };
}

ものすごいほしい型推論機能です。早く実装されないかな…

とはいえ、まあ、こういう、並べると気持ち悪いコードが書けますよ、と。 { }new { }new() { }で全部意味が違うという。


小ネタ コレクション初期化子

$
0
0

昨日のオブジェクト初期化子に続き、今日はコレクション初期化子の話。

コレクション初期化子ってのは、例えば以下のようなやつのことです。

// この、{} の部分がコレクション初期化子。
var x = new List<int> { 1, 2, 3, 4, 5 };

このコレクション初期化を使える条件は、Add メソッドを持っていて、かつ、 IEnumerable を実装していることです。

最低限の実装をしてみると、以下のような感じ。

class MyList : IEnumerable
{
    List<int> _list = new List<int>();
    public void Add(int value) => _list.Add(value);
    public IEnumerator GetEnumerator() => _list.GetEnumerator();
}

static void ListSample()
{
    var x = new MyList { 1, 2, 3, 4, 5 };

    foreach (var item in x)
        Console.WriteLine(item);
}

この、コレクション初期化子は以下のように展開されます。

var x = new MyList();
x.Add(1);
x.Add(2);
x.Add(3);
x.Add(4);
x.Add(5);

ここで生じる疑問があります: IEnumerable の実装、要るの?

依存は避けれるなら避けるべきもの

だって、Addメソッドしか使ってなくない?IEnumerableは何にも使ってないよね?

だいたい、C#の文法がIEnumerableに依存しちゃうの? 例えば、foreachであればGetEnumeartorメソッドさえ持っていれば、別にIEnumerableインターフェイスを実装していない型であっても使えます。 LINQもそうで、SelectWhereなど、所定のメソッドさえ持っていれば、クエリ式を使えます。

最近、Build InsiderでTask-likeの話とかも書きましたけど、 言語の文法が何かの型に依存するというのはリスクを持ちます。 可能なら避けるべきものです。

で、コレクション初期化子、IEnumerable 要るの?

たぶん、誤用の防止

まあ、問題になるとすると以下のような例ですかね。

struct Adder
{
    public int Add(int x, int y) => x + y;
}

static void AdderSample()
{
    // こういう誤用を防ぎたかったのかなという気はする
    var x = new Adder
    {
        { 2, 1 },
        { 3, 4 },
        { 5, 9 },
    };
}

Addメソッドだけを条件にしてしまうと、こういうコードが書けてしまう。 で、このAddの呼ばれ方だと、何の役にも立たないわけです。 Adderの内部状態を変えたいわけじゃなてく、単なるオペレーターなわけでして。

もちろん、IEnumerableの実装を義務付けたところで、あえて濫用することはできます。 例えば、以下のような書き方なら現在の仕様でもできます。

class Accumulator : IEnumerable
{
    public int Sum { get; set; }
    public int Add(int value) => Sum += value;

    // 空実装してしまえば、コレクション初期化子の乱用可能
    public IEnumerator GetEnumerator() => throw new NotSupportedException();
}

static void AccumulatorSample()
{
    // コレクションでもないんでもないけど、コレクション初期化子を使える
    var x = new Accumulator { 1, 2, 3, 4, 5 };
    Console.WriteLine(x.Sum); // 15
}

とりあえず空実装。

まあ、意図的にやってるので大して問題にはならないんですが。 Adderみたいなのが意図せずコレクション初期化子で使われるのだけは防止したかったんですかね… そのためにGetEnumeratorの空実装しろと…

小ネタ インデックス付き foreach

$
0
0

foreach ステートメントで、インデックス付きで列挙したいことが時々あります。 今回は、そういうときの対処方法について。 というか、C# 7が待ち遠しくなる話。

配列やList<T>であれば以下のようにも書けます。

for (int i = 0; i < length; i++)
{
    var item = array[i];
    Console.WriteLine($"index: {i}, value: {item}");
}

IEnumerable<T>の場合にはこうは書けず、 現状だと、以下のようにループの外側に1個変数を作る必要があったりします。

var i = 0;
foreach (var item in items)
{
    Console.WriteLine($"index: {i}, value: {item}");
    i++;
}

ループの外側に変数iが漏れるのが嫌なのと、 あと、continueが絡むとi++するのが大変になったりします。

Selectのオーバーロードの1つを使って、以下のような書き方も一応できます。

foreach (var x in items.Select((item, index) => new { item, index }))
{
    Console.WriteLine($"index: {x.index}, value: {x.item}");
}

ただ、これだと無駄にオブジェクトがnewされます(匿名型は参照型なのでヒープ確保が発生します)。ループの中でのヒープ確保はできれば避けたい負担です。 それに、x.itemみたいな書き方がちょっと嫌な感じです。

C# 7であれば、タプルを使うのがいいかもしれません。ついでに、分解構文も使えば多少すっきりします。

foreach (var (item, index) in items.Select((item, index) => (item, index)))
{
    Console.WriteLine($"index: {index}, value: {item}");
}

タプルは値型なので、いくらかヒープ確保が減ります。 また、分解があるおかげでx.とか書く必要がなくなりました。

でもまだちょっとうっとおしいですね。 (item, index) => (item, index)とか毎度書きたくないです。 拡張メソッドを用意しておきたいところ。

public static partial class TupleEnumerable
{
    public static IEnumerable<(T item, int index)> Indexed<T>(this IEnumerable<T> source)
    {
        if (source == null) throw new ArgumentNullException(nameof(source));

        IEnumerable<(T item, int index)> impl()
        {
            var i = 0;
            foreach (var item in source)
            {
                yield return (item, i);
                ++i;
            }
        }

        return impl();
    }
}

これで、以下のように書けます。

foreach (var (item, index) in items.Indexed())
{
    Console.WriteLine($"index: {index}, value: {item}");
}

これなら、まあ、悪くはなさそうです。 こういうメソッド、そこそこ使うことがありそう。

ちなみに、今回はイテレーターを使ってIndexedメソッドを実装しましたが、ガチガチに最適化するなら、以下のように、構造体で実装してヒープ確保をなくすべきかもしれません。

小ネタ チェック例外とUnion型

$
0
0

今日の話は、C# 7で入る型スイッチや、さらにその先で予定されている、パターン マッチングや、代数的データ型(特にUnion型)というもので、例外処理の仕方が変わるかも、という話です。

例外

例外にはいくつかの性質があります。 まず、挙動としては、以下の2点が特徴的でしょう。

  • ロング ジャンプ: 複数のメソッドをまたいで、遠く離れた場所に移動する
  • 型スイッチ: 例外の型を見て分岐処理を行う

例外の使い方・挙動

例外に含められる情報としては、以下の2つが特に有用でしょう。

  • メッセージ(Messageプロパティ): 発生理由を、自由な文章で伝えられる
  • スタック トレース(StackTraceプロパティ): 発生個所を、呼び出し階層すべて含めて伝えられる

例外に含められる情報

これらは便利ではあるんですが、少々過剰気味ではあります。 特に、ロング ジャンプとスタック トレース取得はそこそこ負担が大きな処理で、 例外をthrowしてしまうと、正常動作と比べて2~3桁くらい動作が遅くなったりします。

この大きな負担が嫌で、例外を嫌う人も多いわけです。 少なくとも、準正常系に対して例外を使うのは過剰スペックじゃないかという話になります。

じゃあ、代わりに何が使えるかというと、最近注目されているのがUnion型とパターン マッチング。

例外の使い方

先に過剰スペックじゃないかという話の方を。

例外って言っても、以下の2つに分けられたりするわけです。

  • 異常系: 完全に想定外の事態
  • 純正常系: あんまり起きてほしくはないけども、想定の範囲内で、対処方法がわかってて処理を続けられる

異常系

異常系の「完全に想定外」っていうのは、まあ、「バグが残ってる」という類なので、理想を言えばテストの段階でなくしたいものです。

といっても、バグを100%取れれば苦労はしないわけで、実際には残ります。 バグが残っていた場合の対策も必要なわけで、たいてい、fail-fastでログだけ残して処理を止めます。

処理の止め方としては、まあ状況によってはFailFastメソッドとかで完全にプロセス停止させてもいいんですが、だいたいは今いるページのリロードとか、ログオフしてログイン画面に戻すとか、そういう対処になるんじゃないでしょうか。

こういう用途には、ロング ジャンプやスタック トレース情報は非常にありがたいです。

  • 根っこの方で1か所catchを書くだけでいい
  • どこで発生したかがわかって、その後、バグ修正がはかどる

ということで、こういう用途なら、例外は便利なものです。

準正常系

一方で、準正常系、要するに「嫌だけど、まあ避けれない」、「起きることは知ってる」、「対処もできる」みたいなものもあって、こちらにロング ジャンプやスタック トレースが要るかと言われると、かなり微妙。たいていは、呼び出し元のメソッドですぐにエラーを調べて対処するものなので、長距離移動はしないし、どこで発生したかもスタック トレースを見るまでもなくわかっています。

ということで、C#だと、あんまりこの用途で例外を使うことはないんじゃないかと思います。

一方で、チェック例外を持っているJavaだと、

  • 異常系には実行時例外を使う
  • 準正常系にはチェック例外を使う

みたいな例外の使い方をしたりもします。 チェック例外にはチェック例外のいいところがあって、それが準正常系の処理と相性が良かったという面があります。

チェック例外

準正常系の例として、平方根を求める関数を考えてみましょう。 いろんな言語で、標準の平方根を求めるsqrt関数は以下のような実装になっています。

  • 浮動小数点数(C# だとdouble)を受け取って、浮動小数点数を返す
  • 負の数が来るなど、計算できない値だった場合、戻り値はNaN (Not a Number)を返す

これを、諸事情あって、以下のような挙動に変える必要があったとします。

  • 引数がそもそもNaNだったらInvalidArgumentエラーとする
  • それ以外で、戻り値がNaNになる(引数が負の数)場合、InvalidResultエラーとする
  • それ以外は普通に標準ライブラリを使って平方根を計算して返す

チェック例外を持つJavaだと、以下のように書いたりします。

まず、エラーを表現するために、例外型を定義。

class InvalidArgumentException extends Exception { }
class InvalidResultException extends Exception { }

平方根を求める関数は、以下のような静的メソッドになります。

private static double sqrt(double value) throws InvalidResultException, InvalidArgumentException
{
    if (Double.isNaN(value)) throw new InvalidArgumentException();
    if (value < 0) throw new InvalidResultException();
    return Math.sqrt(value);
}

このメソッドを呼び出す側は例えば以下のようになるでしょう。

try
{
    double y = sqrt(x);
    System.out.println(y);
}
catch(InvalidArgumentException b)
{
    System.out.println("引数の時点でおかしな値");
}
catch(InvalidResultException a)
{
    System.out.println("計算結果がおかしな値");
}

チェック例外のメリットは2つあります。

  • 型の明示(denotation): メソッド定義側で、どういう例外が出るかを明示できる
  • 完備検査(completeness check): 呼び出し側で、出る可能性のある例外をcatchしなかったらコンパイル エラーにできる

この2つは非常に重要な観点なので確かに欲しい機能なんですが、 その一方で、以下の2点が不満という話になります。

  • 戻り値とthrows句の2カ所に分かれた書き方は本当にいいのか
  • おまけでロング ジャンプやスタック トレース情報が付いてくるのは過剰ではないか

Union型とパターン マッチ

ロング ジャンプやスタック トレース取得は要らない、 型の明示と完備検査はほしい… となったときに、関数型言語の類だとちょうどそういう仕組みを持っていたりします。 Union型(直和型)とパターン マッチです。

例えばF#で先ほどと同様のsqrt関数を書こうと思うと、 まず、正常な値か、InvalidArgumentエラー、InvalidResultエラーを表すUnionを作ります(F#の場合は判別共用体(discriminated union types)と言います)。

type SqrtResult =
    | Value of Double
    | InvalidArgument
    | InvalidResult

これを使って、平方根を求める関数は以下のように書けます。

let sqrt x =
    if Double.IsNaN(x) then InvalidArgument
    elif x < 0.0 then InvalidResult
    else Value(Math.Sqrt(x))

呼び出す側は以下のとおり。

match sqrt(x) with
| Value y        -> Console.WriteLine(y)
| InvalidArgument -> Console.WriteLine("引数の時点でおかしな値")
| InvalidResult   -> Console.WriteLine("計算結果がおかしな値")

ちゃんと要件は満たしています。

  • 型の明示: 判別共用体を使って、InvalidArgumentInvalidResultが返る可能性を明示できている
  • 完備検査: 判別共用体に対するmatch (C#でいうswitch)では、条件が足りていなかったら「Incomplete pattern matches」(パターンが不完全です)という警告が出る
  • ロング ジャンプやスタック トレース取得は要らない

完備検査の課題

チェック例外にしても、Union型にしても、完備検査をするためにはちょっとした課題があったりします。 何かというと、後からのパターン追加がかなり厳しい。

エラーのパターンを増やしたとしましょう。例えば、この例で言うと、NaNだけじゃなくて∞もエラーにしたくなって、InvalidArgumentとは別にInfiniteエラーというのを返したくなったとします。

private static double sqrt(double value) throws InvalidResultException, InvalidArgumentException, InfiniteException
{
    if (Double.isInfinite(value)) throw new InfiniteException();
    if (Double.isNaN(value)) throw new InvalidArgumentException();
    if (value < 0) throw new InvalidResultException();
    return Math.sqrt(value);
}
let sqrt x =
    if Double.IsInfinity(x) then Infinite
    elif Double.IsNaN(x) then InvalidArgument
    elif x < 0.0 then InvalidResult
    else Value(Math.Sqrt(x))

呼び出し側も修正しないと、Javaの場合はコンパイル エラー、F# の場合は警告になります。(ビルド設定で「警告もエラー扱いする」という項目もある以上、警告の追加も破壊的変更です。)

おかしな挙動をするくらいならコンパイル時にエラーや警告が出た方が傷は浅くて済むんですが、コードが大規模化してくると結構厄介な問題になります。

  • 関数を作る人と使う人が全然違う。場合によっては組織をまたぐ
    • 使う人にとっては意図しない(例えば別件にかかりきりで修正できないような)タイミングでも、修正を強要される
  • パッケージも分かれる
    • 使う側の再コンパイルなしでパッケージのバージョンを上げたいのに、再コンパイルしてみないと結局、完備検査が働かない

C# で準正常系の処理

さて、やっとC#に関してなんですが、C#で準正常系の処理を書くとしたら、

  • 現状: エラー コードを返すような昔ながらの処理に先祖返り
    • 型の明示はできてる
    • 完備検証はできてない
  • 現在検討されていること:
    • C#にもUnion型の追加
    • switchステートメントでの完備検証の追加

という感じになっています。

現状だと、どうしても以下のような感じに落ち着いたりします。

まず、エラーを示すための列挙型を作ります。

using System;

enum ErrorType
{
    None,
    InvalidArgument,
    InvalidResult,
}

class Program
{
    static ErrorType TrySqrt(double x, out double y)
    {
        y = 0;
        if (double.IsNaN(x)) return ErrorType.InvalidArgument;
        if (x < 0) return ErrorType.InvalidResult;
        y = Math.Sqrt(x);
        return ErrorType.None;
    }

    static void Main()
    {
        var data = new[] { double.NaN, -1.0, 2.0 };

        foreach (var x in data)
        {
            Console.Write(x + " → ");
            double y;
            switch (TrySqrt(x, out y))
            {
                case ErrorType.None:
                    Console.WriteLine(y);
                    break;
                case ErrorType.InvalidArgument:
                    Console.WriteLine("引数の時点でおかしな値");
                    break;
                case ErrorType.InvalidResult:
                    Console.WriteLine("計算結果的におかしな値");
                    break;
            }
        }
    }
}

正直なところ、この先祖返り感は結構な残念さではあるんですが。 先祖返りが起こるってのは、現状(例外機構)に対して結構な不満があるという証でもあります。

将来的に、これがどうなってほしいかというと、以下のような感じでしょうか。

using System;

enum ErrorType
{
    InvalidArgument,
    InvalidResult,
}

class Program
{
    static double | ErrorType M(double x)
    {
        if (double.IsNaN(x)) return ErrorType.InvalidArgument;
        if (x < 0) return ErrorType.InvalidResult;
        return Math.Sqrt(x);
    }

    static void Main()
    {
        var data = new[] { double.NaN, -1.0, 2.0 };

        foreach (var x in data)
        {
            Console.Write($"{x} → ");
            Console.WriteLine(M(x) match
            {
                double y: y.ToString(),
                ErrorType.InvalidArgument: "引数の時点でおかしな値",
                ErrorType.InvalidResult: "計算結果的におかしな値",
            });
        }
    }
}

パーツごとに説明すると、以下のようなものから成り立ちます。

  • Union型(確度 低)
    • double | ErrorTypeみたいな書き方でUnion型を定義
    • この書き方は TypeScript を習ったもの
    • C#でこの書き方ができるようになるかというと微妙。後述する「abstract sealed」なクラスならできるようになるかも
  • 型による分岐(C# 7で入る)
    • switchステートメントのcaseや、is演算子で、型を見た分岐ができるようになる
  • match 式(確度 高)
    • switchを式にしたもの
  • Union型とEnum型の完備検査(確度 高)

Union型は、要するに、A | B と書いた場合、ABかのどちらかの値を持つ型です。 一応、これをC#で似たようなことしようと思うと、以下のように書くことになります。要するに、ただ単にクラスの継承階層を作るだけ。

abstract class AorB { }
class A : AorB { }
class B : AorB { }

ここで問題は、AorBを赤の他人が継承して使えることです。 「完備検査の課題」で説明した通り、完備検査をする場合、パターン追加が破壊的変更を起こします。 AorBを作った人が意図して破壊的変更を起こすならまだしも、 作った人でも使う人でもない第3者のせいでコンパイル エラーが起きるのはさすがに許容できないでしょう。

一応、第3者による継承を防止する手段はあって、以下のように書きます。

// AorB 自体のインスタンスを作れないように abstract
abstract class AorB
{
    // クラスの外からの派生を禁止するためにコンストラクターを private に
    private AorB() { }

    public class A : AorB { }
    public class B : AorB { }
}

ですが、この書き方はネストするのがうざい。 ということで、以下のような書き方で、上記のネスト状態のコードに展開したいという案が出ています。

// 継承前提(abstract)なんだけど、意図しない継承はさせたくない(sealed)という意図で
// abstract sealed と付ける
abstract sealed class AorB { }
class A : AorB { }
class B : AorB { }

abstract sealedという、現在のC#的にいうと矛盾する2つの修飾子を組み合わせて、 継承して使う前提だけど、意図しない第3者には継承させないという型を作ることができます。 こういう構文で、Union型に類するものを提供しようという感じです。

ちなみに、F#の判別共用体は、内部実装的にはこれと同じようなコード(privateなコンストラクター + ネストしたクラス)を生成しています。

小ネタ 同期コンテキストを拾わないTask型

$
0
0

今日も小ネタというかC# 7ネタというか、C# 7に合わせて1個ライブラリ書いたというか。

勢いで、こんなライブラリ1個作ってしまいました。 C# 7向けです(半分くらいはC# 5.0でも使えるものですが)。

ということで、C# 7の機能の1つについて説明。 C# 7で以下のような機能が入ります。

ほぼ、ValueTaskのために入った機能なんですが、まあ、せっかくなので他でも使ってみようというのが今日の話。

同期コンテキスト

C#に限らずいろんなプログラミング言語で、非同期処理の後にメイン スレッドに戻ってこないといけないという制約があったりします。 特に、GUIプログラムの開発環境だとたいてい、UIがらみのクラスはメイン スレッド(UI末ラッド)からしか触れないとかそういう制約があります。

こういう、「メイン スレッドに戻らないといけない」とか、その場その場にある文脈を同期コンテキストと言います。 C#でawaitを使って非同期処理をする場合、 awaitした時点で同期コンテキストを持っていたら、 それを拾って元のコンテキストに戻ってくるようになっています。

同期コンテキストに関する説明、参考URLを探そうとしたものの… 意外とこの時期、真面目に自分のサイトを更新してなくて「書きかけ」ばっかり… 一番真面目に書いてあるのが@ITで書いたSilverlightの記事という… (XamarinとかASP.NET Coreあたりで書き直したい気もしつつ。)

とにかく、C#には同期コンテキストってものがあって、通常、awaitするとそのコンテキスト拾って、スレッド プールからメイン スレッドとかに自動的に戻ってきてくれる仕組みが入っています。

コンテキスト拾いすぎ

とはいえ、これはアプリのレイヤーのためにある機能であって、 逆に、ライブラリの中でコンテキストを拾っちゃうとまずかったりします。 意図しないタイミングでメイン スレッドを止めてしまって、デッドロックを起こしたりします。

ということで、ライブラリ作者は、同期コンテキストを拾わないようにするために、以下のようなコードを書くことを強要されます。

// ConfigureAwait で同期コンテキストを拾うかどうか設定できる
// 引数を false にすると拾わない
await FAsync().ConfigureAwait(false);

ライブラリを書く側の人は毎度毎度、これで苦労します。 正直に言って結構うざい…

コンテキストを拾わない Task

ってことで作ったのが ContextFreeTask。 コンテキストを拾わないTaskです。

冒頭の通り、C# 7では非同期メソッドの戻り値の型を任意に変えれるようになったので、自作してみました。

// Task の代わりに ContextFreeTask を非同期メソッドの戻り値にできる
// この中にある await は同期コンテキストを一切拾わない
private async ContextFreeTask FAsync()
{
    // この時点でどんなコンテキストで動いていようと…
    await Task.Delay(100);
    // コンテキストは拾われないので、元のコンテキストには戻らない
    await Task.Delay(100);
    // 同上、戻らない
}

private async Task GAsync()
{
    // ContextFreeTask に対する await もできる
    // この await も同期コンテキストを拾わない

    await FAsync();
    // コンテキストは拾われない
}

概ね、以下のコードと同じ挙動になります。

private async ContextFreeTask FAsync()
{
    await Task.Delay(100).ConfigureAwait(false);
    await Task.Delay(100).ConfigureAwait(false);
}

private async Task GAsync()
{
    await FAsync().ConfigureAwait(false);
}

戻り値があるとき用、すなわち、Task<TResult> の代わりの ContextFreeTask<T> もあります。

private async ContextFreeTask<string> HAsync(int n)
{
    await Task.Delay(100);
    return n.ToString();
}

中身

Task 1個だけ持つ薄いラッパー構造体で、 ほとんどの処理はTaskや、そのawaiter、async method builderへの丸投げです。 その手前にConfigureAwait(false)SetSynchronizationContext(null);を挟んでいるだけ。

public struct ContextFreeTask<T>
{
    public Task<T> Task { get; }
}

public struct ContextFreeTaskAwaiter : ICriticalNotifyCompletion
{
    private readonly Task _value;
    public void OnCompleted(Action continuation) => _value.ConfigureAwait(false).GetAwaiter().OnCompleted(continuation);
}

public struct AsyncContextFreeTaskMethodBuilder
{
    private AsyncTaskMethodBuilder _methodBuilder;

    public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
        where TAwaiter : INotifyCompletion
        where TStateMachine : IAsyncStateMachine
    {
        SynchronizationContext.SetSynchronizationContext(null);
        _methodBuilder.AwaitOnCompleted(ref awaiter, ref stateMachine);
    }
}

やってみて

とりあえず小ネタというか、単にライブラリ紹介だったわけですが…

まあ、この、任意の型を非同期メソッドの戻り値に使える機能、 「C# 7の新機能紹介」でもどう取り扱うかは結構悩みまして。 何せ、実用例がほんとに少ない。 なので、どうしても「ほぼ ValueTask 専用です」的な書き口に。 (ValueTask だけで十分価値は高いんですが。)

一応小ネタっぽい話もすると、この機能、C#チームからも

We estimate that in the eventual C# ecosystem maybe 5 people will write tasklike types that get mainstream adoption.

我々の見積もりでは、最終的に C# エコシステム内において、たぶんせいぜい5人くらいが、メインストリームに採用される tasklike 型を書くことになるだろう。

とか言われています(参考: C# LDM notes from 2016.08.24)。 せいぜい5人。

たぶん確実に使われそうなのとしては、

の3つは確定。 残りせいぜい2個ですか。

まあ、ContextFreeTaskも、作ったはいいけど、大々的に使うかどうかはちょっと悩ましかったり。 誤用(逆に使うとまずいアプリのレイヤーで使われたり。ライブラリでも、publicなところで使ってしまうと、アプリ側でコンテキストを拾い損ねる事態になりそう)がちょっと怖そうですし。

小ネタ フォーマット文字識別子

$
0
0

いい加減、小ネタらしい小ネタを書かないとタイトル詐欺臭いのでほんとに小ネタを。

C#では、以下のようなコードが書けたりします。変数abを用意して、a\u200dbって変数に書き込むと、abの値が変わるという。 要するに、この2つは識別子としては同一扱いされます。

using System;

class Program
{
    static void Main()
    {
        var ab = 0;
        a\u200db = 1; // ab と同じ扱い。\u200d は Zero Width Joiner
        Console.WriteLine(ab); // 1
    }
}

この挙動を説明するには、以下の2つの仕様が出てきます。

1つのUnicodeエスケープ シーケンスは、\uに続けて4桁の16進数を打つか、\Uに続けて8桁の16進数を打つと、その番号に対応したUnicode文字に変換されるというものです。このエスケープ シーケンスは、文字列リテラルの外、どこででも有効です。例えば、以下のようなことも可能。aの文字は、UnicodeではU+61です。

using System;

class Program
{
    static void Main()
    {
        var a = 0;
        \u0061 = 1;
        Console.WriteLine(a);
    }
}

もう1つは、フォーマット文字は識別子に含められるけど、除外して考えるという仕様。 フォーマット文字ってのは、文字を描画方法とかを指定するための不可視文字で、例えば以下のようなものがあります。

  • Zero Width Joiner (U+200D): その左右の文字が不可分なことを表す。ゼロ幅接合子。略称 ZWJ。
  • Left-to-Right Mark (U+200E): 文字を左から右に向かって描画すべきということを表す
  • Right-to-Left Mark (U+200F): 同上、右から左

一部の自然言語でこの手の制御が必要だけども、見えない文字だからこの文字のあるなしで別識別子にはしたくないっていうことでしょう。

これら2つを組み合わせた結果が冒頭のコードです。a\u200dbは、abの間にZWJを挟んだ状態で、結果的に、識別子としてはZWJが無視されて、abとして扱われます。

まあ、見えない文字とか無視すべきですよね。 普通、見えない文字はそもそも識別子として使えなくしてるものなんですが、Right-to-Left Markとかは、アラビア語プログラミングとかすると使うかもしれないですもんねぇ。「使う」というか、もしかしたらエディターによって勝手に挿入されるかもしれず。 その場合、無視すべき、ということなんでしょう。

無視しないプログラミング言語もありますが

Unicode Consortium で規定

この挙動、どうも、Unicode Consortiumのレポートに基づいてるみたいです。

Unicode Technical Report #15の、Annex7: Annex 7: Programming Language Identifiersのところ。 プログラミング言語の識別子に使える文字はどうあるべきか、みたいな話が結構詳細に書かれています。 これに沿っている言語は他にもありそうなので、試してみるといいかも。

小ネタ 隠しメンバー

$
0
0

今日は、C# 上からは見えない隠しメンバーが作られるという話。 「覚えがないのになぜか『すでに定義があります』っていう名前被りのコンパイル エラーが出た」なんてこともあり得ます。

C# は .NET 向け言語の代表的な位置づけの言語ではありますが、だからと言って、C# の機能と .NET IL (中間言語)の機能は同じではありません。 C# コンパイラーによって、ILのレベルでは結構身に覚えのないメンバーが追加されます。

例えば以下のようなコードを書くだけで、自動的に追加されたメンバーがたくさん出てきます。

using System;
using static System.Console;
using static System.Reflection.BindingFlags;

class C
{
    public int X { get; set; }
    public int this[int index] { get { return index; } set { } }
    public event Action E;
}

class Program
{
    static void Main()
    {
        foreach (var x in typeof(C).GetMembers(Public | NonPublic | Instance | DeclaredOnly))
        {
            WriteLine(x.Name);
        }
    }
}

実行結果は以下の通り。

get_X
set_X
add_E
remove_E
get_Item
set_Item
.ctor
X
Item
E
<X>k__BackingField
E

今日はこれらについて説明して行きます。

コンストラクター

これはまあ、わかりやすいですね。C# のクラスは、明示的にコンストラクターを書かなくてもnew Class()と書けるわけで、暗黙的にコンストラクターが1つ作られています(クラスで、コンストラクターを1つも書かなかった場合だけ)。

リフレクション的には、コンストラクターは.ctorという名前で見えます。 ちなみに、生成されるILを覗いてみると以下のような感じ。

.class private auto ansi beforefieldinit C
       extends [mscorlib]System.Object
{
  .method public hidebysig specialname rtspecialname 
          instance void  .ctor() cil managed
  {
    .maxstack  8
    IL_0000:  ldarg.0
    IL_0001:  call       instance void [mscorlib]System.Object::.ctor()
    IL_0006:  nop
    IL_0007:  ret
  }
}

rtspecialnameとかいう特別そうなフラグが付いているのと、 名前が変という以外はただのメソッドです。 中身も親クラスのコンストラクターを呼んでいるだけ。

プロパティ

プロパティは、getsetに応じたメソッドと、 自動実装プロパティであればフィールドが1つ作られます。 今回の例では、Xは自動実装プロパティで、getset共に持っているので以下のようなILが生成されます。

  .field private int32 '<X>k__BackingField'
  .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) 

  .property instance int32 X()
  {
    .get instance int32 C::get_X()
    .set instance void C::set_X(int32)
  }

  .method public hidebysig specialname instance int32 
          get_X() cil managed
  {
    .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) 
    .maxstack  8
    IL_0000:  ldarg.0
    IL_0001:  ldfld      int32 C::'<X>k__BackingField'
    IL_0006:  ret
  }

  .method public hidebysig specialname instance void 
          set_X(int32 'value') cil managed
  {
    .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) 
    .maxstack  8
    IL_0000:  ldarg.0
    IL_0001:  ldarg.1
    IL_0002:  stfld      int32 C::'<X>k__BackingField'
    IL_0007:  ret
  }

意味的には以下のような感じ。

  • <>が含まれる読めた代物じゃないフィールドができる
  • get_set_から始まるメソッドができる
  • フィールドとメソッドにはCompilerGenerated属性が付いてる
  • プロパティの定義自体は、メソッドを参照しているだけ

フィールドは、通常のC#では書けないような記号入りの名前なので特に問題を起こさないんですが、 メソッドの方は被りがあり得ます。つまり、以下のコードはコンパイル エラーを起こします。

class C
{
    public int X { get; }
    int get_X() => 0;
}

しかもエラーを起こすのは get のところ。

インデクサー

インデクサーなどというものは存在しない。いいね?

C#のインデクサーは、ILのレベルではItemという名前のプロパティになっています。

  .custom instance void [mscorlib]System.Reflection.DefaultMemberAttribute::.ctor(string) = ( 01 00 04 49 74 65 6D 00 00 ) // ...Item..

    .property instance int32 Item(int32)
  {
    .get instance int32 C::get_Item(int32)
    .set instance void C::set_Item(int32,
                                   int32)
  }

  .method public hidebysig specialname instance int32 
          get_Item(int32 index) cil managed
  {
    .maxstack  1
    .locals init ([0] int32 V_0)
    IL_0000:  nop
    IL_0001:  ldarg.1
    IL_0002:  stloc.0
    IL_0003:  br.s       IL_0005

    IL_0005:  ldloc.0
    IL_0006:  ret
  }

  .method public hidebysig specialname instance void 
          set_Item(int32 index,
                   int32 'value') cil managed
  {
    .maxstack  8
    IL_0000:  nop
    IL_0001:  ret
  }

意味的には以下のような感じ。

  • Itemという名前のプロパティが作られる
  • プロパティは実は引数を取れる
    • (C#では無理だけど、VBなら引数付きプロパティも書ける)
  • DefaultMember属性でItemプロパティを指定している

つまるところ、インデクサー = 「名前を省略していい引数付きプロパティ」です。

Itemに展開されるので、もちろん、以下のコードはthisのところでコンパイル エラー。

class C
{
    public int this[int index] { get { return index; } }
    int Item { get; }
}

get_Itemメソッドもダメです。getのところでエラー。

class C
{
    public int this[int index] { get { return index; } }
    int get_Item(int index) => 0;
}

Itemプロパティは普通に使いそうな名前なので、罠を踏むとしたらこれが一番頻出しそうなやつです。

ちなみに、回避方法も、まあ、あって、インデクサーから生成されるプロパティの名前は変更できます。

class C
{
    [System.Runtime.CompilerServices.IndexerName("Indexer")]
    public int this[int index] { get { return index; } }

    // ↑これで Item は生成されなくなるので、自前のもの↓と被らなくなる

    int Item { get; }
    int get_Item(int index) => 0;
}

ちなみに、C#コード上はインデクサーにIndexerName属性が付いていますが、 コンパイル結果的にはクラスに対するDefaultMember属性に変換されます。

イベント

最後は、他の言語から来た人が困惑する機能ナンバー1、イベントです。 もっと難しい機能もたくさんありますけど、利用頻度の割に複雑という意味では断トツではないかと。 (使う頻度は多少あるけど、作る頻度はかなり低いんじゃないでしょうか。)

プロパティに近いんですが、getsetの代わりにaddremoveです。 自動実装でフィールドが作れる部分は同じです。

  .event [mscorlib]System.Action E
  {
    .addon instance void C::add_E(class [mscorlib]System.Action)
    .removeon instance void C::remove_E(class [mscorlib]System.Action)
  } // end of event C::E

  .field private class [mscorlib]System.Action E
  .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) 

    .method public hidebysig specialname instance void 
          add_E(class [mscorlib]System.Action 'value') cil managed
  {
    .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) 
    // 結構長いのでさすがに省略
  }

  .method public hidebysig specialname instance void 
          remove_E(class [mscorlib]System.Action 'value') cil managed
  {
    .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) 
    // 結構長いのでさすがに省略
  }

プロパティと似た感じで、

  • フィールドができる
  • add_remove_から始まるメソッドができる
  • フィールドとメソッドにはCompilerGenerated属性が付いてる
  • イベントの定義自体は、メソッドを参照しているだけ

という状態なんですが、ちょっと違うのは、以下の部分。

  • フィールドの名前がイベントの名前とまったく同じ(この例の場合E)
  • 自動生成されるメソッドの中身はほんと長い(参考: 補足: 自動イベント)

C#では許されていませんが、ILレベルだと、メンバーの種類が違えば同じ名前を使えます。

そして、実は、イベントを触っているように見えて、実は裏で作られたフィールドを触っているという事態に。

class C
{
    public event Action E;

    // 登録の側は add_E が呼ばれてるんだけど
    public void Register() => E += Handler;
    void Handler() { }

    // 呼び出し側では、実はイベントの E じゃなくて、フィールドの E
    public void Invoke() => E();
}

このInvokeメソッドの中を見てみると以下のような感じ。ldfld命令はフィールド読み込みのための命令です。

.method public hidebysig instance void  Call() cil managed
{
  .maxstack  8
  IL_0000:  ldarg.0
  IL_0001:  ldfld      class [mscorlib]System.Action C::E
  IL_0006:  callvirt   instance void [mscorlib]System.Action::Invoke()
  IL_000b:  nop
  IL_000c:  ret
}

つまり、イベントを明示的に実装すると、E()みたいな呼び出しはできなくなります。

class C
{
    private Action _e;
    public event Action E
    {
        add { _e += value; }
        remove { _e -= value; }
    }

    // 明示的に add/remove を実装すると、自動実装なフィールドの E が消える
    // ↓このコードが書けなくなる
    public void Invoke() => E();
}

イベントの明示的な実装とかめったにするものじゃないのでそんなに踏まないと思いますが、一応注意が必要です。

小ネタ 隠し演算子(?)

$
0
0

C#には隠しキーワードとして__makerefなどの見慣れないキーワードがあるという話はたまに話題に出てきますが、実は隠し演算子もあります。

演算子 同じ結果の式 意味合い
-~x x + 1 xに向かって値が入っているイメージ
~-x x - 1 xから値が出ていくイメージ

副作用を起こさない(非変)インクリメント、デクリメント(non-modified increment/decrement)です。 実際、以下のようなコードを実行することができます。

var a = 10;
Console.WriteLine(-~a); // 11
Console.WriteLine(~-a); // 9

ideoneとかでも実行できます。 ideoneは確かMonoで動いているはずで、MonoのC#コンパイラーもひそかに対応しているということですね。

こいつは、演算子の形状からtadpole (オタマジャクシ)とか言われたりもします。 (?. なんかもエルビス プレスリーの髪型っぽく見えるという話からelvis演算子とか呼ばれたりもします。それと同種の愛称です。)

x + 1でいいじゃないかと思うかもしれませんが、 単項演算子なので優先度が高いという利点があります。 (x + 1) * (y - 1)というような式が、`-~x*~-y'となります。

一方で、単項演算子の±1であれば、++ (インクリメント)と-- (デクリメント)もあります。 しかし、これらは副作用を伴っていて、xの値を書き換えてしまいます。

演算子 同じ結果の式
++x x += 1
--x x -= 1

副作用を伴う式というはあまり行儀がいいものではないので、その副作用なし版がこっそり用意されているというのが-~~-です。

まあ、利点と言っても微々たるものですし、どっちがプラスでどっちがマイナスかわかりにくいですから、正式採用とはならず、仕様書等には載っていません。





種明かし

さて、まあ、嘘なわけですが。 実行できる嘘。

昔同じネタをちょっと取り上げたことはあるんで覚えてらっしゃる方もいらっしゃいますかね。

要するに、

という話だったんですが…

真に受ける人が多いということは、ちゃんとした解説書かなきゃダメなのかなというのが、今回の本当の主題。

実際、つい最近も、2の補数表現の話とかを知らない人にこの話を説明したりしたんですよね。ピックアップRoslynのついででさらっと流すのはもったいなかったかなぁと。

本当は -~ の組み合わせ

これ、要するに、以下の意味です。

括弧で整理
-~x -(~x)
~-x ~(-x)

単項演算子が2個並んでいるだけ。

たぶん、3つのはまりどころがあります。

  • ~演算子とかめったに使わない
  • ビット反転とマイナスの関係を知らない人がそこそこいる
  • 単項演算子をくっつけて書くのはどうなのよ

ビット反転

真に受ける人が多かった理由の1つは、~演算子とかめったに使わないからでしょうね。 そもそも~がC++とかC#で有効な演算子なことを知らない/忘れている人がそれなりにいるんでしょう。 というか、ビット操作とか2進数での数値表現がまず苦手って人も見ますもんねぇ、ちらほら。

~はビット反転の意味です。以下の例のように、すべてのビットの0と1を逆にします。

ビット反転

ビット反転とマイナスの関係

次に必要な説明は、ビット反転とマイナスの組み合わせでどうして±1になるのかです。 ビット反転~とマイナス-は、常に以下の式を満たします。

~x + 1 == -x

例えば、以下のようなコードを書くと、throwの行は通らずプログラムが正常終了します。

using System;

class Program
{
    static void Main()
    {
        for (int x = 0; x < 256; x++)
        {
            if (~x + 1 == -x) continue;

            throw new InvalidOperationException();
        }
    }
}

これは、以下の図のように考えれば説明が付きます。

ビット反転 + 1

  1. 元の数字と、ビット反転した数字を足すと、全ての桁で 1 + 0 が起きるわけで、 結果は全桁1になります
  2. これに1を足すと、桁上がりによって0が並びます
  3. オーバーフロー(桁上がりしてしまった最上位桁)を無視すると、完全に0になります
  4. すなわち、x~x + 1を足すと0になります
  5. -xというのは「xと足して0になる数」のことを指すわけで、~x + 1-xと等しいはずです。

実際、符号付き(この例で言うと8 bitなのでsbyte)の-123と、 符号なし(byte)の133 = 256 - 123は、ビット表現としては全く同じ1000 0101になります(123のビット反転は132です。132 + 1 = 133 = -123)。

この考え方は、コンピューターのハードウェアを作る上で非常に便利です。

  1. 負の数を「ビット反転 + 1」で表すものとする
  2. この場合、a - ba + ~b + 1となる
  3. つまり、加算回路をそのまま流用して減算回路を作れる

ということで、だいたいのハードウェアでこの「ビット反転 + 1」で負の数を表現する方式が採用されています。 この表現形式を「2の補数(two's complement)表現」と言います。 わざわざ「2の」とかいう名前が付いているのは、他の方式もあったからなんですが、まあ、現存していません。

単項演算子をくっつけて書く

あとは、演算子をくっつける書き方自体がどうなのか、という話はあります。

だって、識別子(変数名とか)の場合、aba bは違う意味になるじゃないですか。 前者は「ab」という名前の1つの識別子、後者「a」と「b」が並んでる。 なのに、~-~ -は同じ意味で、~-に分解される。

~-みたいな、複数文字で1つの演算子にできれば、演算子オーバーロードに幅ができて便利かもしれません。 実際、F#なんかはそれを認めています。

C#でも、~-みたいに書かれると、一瞬それを期待しちゃうのかもしれません。 でも、実際には、~-は2つに分解されて、複数文字1演算子はできません。

おまけ: くっつけて書けるとそれはそれでひどい

常に一貫して「演算子は1文字1文字全部区切る」ってルールならまだしも、いくつか例外が存在しているのがまた面倒です。 ++ (インクリメント)とか>= (大なりイコール)とか=> (ラムダ式の矢印)とかは2文字で1演算子。だったら、「~-で1演算子」も期待しかねない。

インクリメントなんて、+++がそれぞれ別の意味になるもんだから本当にひどくて。 a+++bって式はC#などでは有効な式なんですが、どういう意味になるかぱっと見ではわからない。なんせ、以下の3つの式、どれも有効で、それぞれ違う結果になります。

  • a++ +b
  • a+ ++b
  • a+ + +b
using static System.Console;

class Program
{
    static void Main()
    {
        int a, b;

        a = 1; b = 1;
        WriteLine($"{a++ +b}, {a}, {b}"); // 2, 2, 1: (a++) + b
        a = 1; b = 1;
        WriteLine($"{a+ ++b}, {a}, {b}"); // 3, 1, 2: a + (++b)
        a = 1; b = 1;
        WriteLine($"{a+ + +b}, {a}, {b}"); // 2, 1, 1: a + (+(+b))
        a = 1; b = 1;
        WriteLine($"{a+++b}, {a}, {b}"); // 2, 2, 1: つまり、a+++b は a++ +b 扱い
    }
}

Swiftなんかだと、単項演算子を隣接させるのを禁止してるみたいですね。 -~xはエラーになります。

かつ、単項演算子とオペランドの間は逆にスペースを挟むのを禁止しているようで。 - ~xという書き方もエラー。 括弧を使って-(~x)と書くのを義務付けているようです。

まあ、確かに括弧が付いている方が見やすくて人的ミスは起こしにくいでしょう。 一方で、関数適用はf (x)とか書けるのに、単項演算子は~ (x)とは書けないっていうちょっと気持ち悪い状態ではあります。


.NET Global Tools

$
0
0

.NET Core 2.1 の Preview 1 が公開されたそうで

以前から、daily build の不安定な奴で良ければ試せていたんですが、オフィシャルにアナウンスがあったということは、作業が一区切りしたということでしょう。

実際、今回の主題の Global Tools は、以前、daily build で試したときには全然動いていませんでした。 ということで、今日、やっと動いたので試してみたという話。

Global Tools

.NET Core 2.1 の、dotnet コマンドの新機能の1つです。

NPM global toolsにインスパイアされて作ったよ、というもの。

要するに、dotnetコマンドを使って、NuGet 越しにインストール可能なコマンドラインツールを提供するための仕組み。

試しに作ってみたものがこちら:

ツールの作り方

csprojPropertyGroup に、以下のような行を足せばいいらしい。

<PackAsTool>true</PackAsTool>

ただ、現状、Visual Studio の方はまだ対応していなくて、これを入れたプロジェクトをビルドしてもそのままだとパッケージは作られません。dotnet packコマンドを手打ちする必要があります。

dotnet pack -c release cszip/cszip.csproj

詳しくは、公式サンプル: dotnetsayを参照。

ツールのインストールの仕方

dotnet install tool コマンドでインストールできます。

dotnet install tool -g cszip --configfile .\nuget.config

NuGet を使ってパッケージを取ってきて、ユーザー フォルダーの下の .dotnet/toolspkgs.dotnet/tools 以下にもってきて使うようです。 作ったツールはnuget.orgにアップロードするもよし、 ローカル フォルダーを NuGet パッケージ ソースに指定するもよし。

上記の例では、設定ファイルを指定して、ローカルのフォルダーから作ったツールをインストールしています。

ツールの使い方

.dotnet/tools にはパスが通っているので、作ったコマンド ライン アプリがどこからでも呼べるようになっています。 普通にコマンドをたたけば呼べます。

cszip packages sample.zip

試しに作ってみたもの

  • cszip: ZipFile.CreateFromDirectory を呼んでるだけ
  • csunzip: ZipFile.ExtractToDirectory を呼んでるだけ
  • xstatic: .NET 標準ライブラリ中の任意の静的メソッドを呼び出せる

割と最近、社内で C# で書いたコマンド ライン ツールを、 他社の方に使っても割らないといけない事案がありまして。 その時のやり取り:

  • 手元でビルドしたバイナリを毎回手渡しするのしんどいです
  • 先方も、Mac だけど dotnet コマンドは使えるのよね? .NET Core SDK はインストールしてもらえてて。ソースコードは共有してるんだし、向こうで dotnet build してもらったら?
  • 試してみてもらったんですが、PostBuild イベントで呼んでるコマンドが PowerShell なので Mac で呼べませんでした

呼んでるのは Compress-Archive でした。 Cygwin でも入れて zip コマンドに変えれば Mac でも動きそうなものの。 Windows だと bat を書いて、Mac とかだと sh を書いてとかもできはしますが、書くのはいいけど動作確認が大変で。 あと、最近は PowerShell も .NET Core 化してるのでこれを入れてもらうという手もなくはないものの。 そこまでやるか…と悩んだ結果、手渡し運用を続行。

ということで、 dotnet コマンドでインストールできて、dotnet コマンドで実行できるものがあるならそれを使くて、 Global Tools の仕組みを使ってみようかということで作ったのが cszip と csunzip。

で、zip/unzip でコマンド分けるべきか、 分けるの面倒ではないか、 というかむしろいっそのこと任意の静的メソッド呼べるようにしてやろうか。 等々、遊んでみてた結果が xstatic の方です。

インスパイア元の NPM Global Tools も似たような動機ですよね、きっと。 node.js でインストールできて、node.js で実行できるツールが欲しいという。

cszip、nuget.org に上げました

$
0
0

こないだ .NET Global Tools を試すのに作ってみた cszipcsunzipnuget.org に上げてみといた。

以下のコマンドでインストール可能な状態になっています。

dotnet instal tool -g cszip dotnet instal tool -g csunzip

以下のように適当にもほどがあるんで nuget.org に上げるかどうか迷っていたものの。

  • readme の類一切ない
  • ほんとに内部的に CreateFromDirectoryExtractToDirectory を呼んでるだけ
    • 例外処理全然してない (不正な引数を渡したら .NET の例外メッセージがそのまま出る)
    • オプション指定とかもできない (常に「圧縮率優先」「UTF8」)
  • 専用のリポジトリ持ってなくて、UfcppSampleのDemoフォルダー以下にある

名前的にも、以下のような点で悩んだものの。

  • C# で書いているというだけで、別に「C# 向け」ではないけど cs~
  • こんな手抜き実装なもので cszip とかいう名前を取っちゃっていいのか

あと、当初目的(前述の通り、クロスプラットフォーム ビルドの面倒をマシにしたい)を考えるとLinuxとかMacでの動作確認しないといけないわけですけどもそれもやっておらず…

まあ、やっちゃってから考えるかと。

Visual Studio 15.6 リリース

$
0
0

なんかVisual Studioの更新に 15.6.0 の正式版が配信されてますね。

ブログとかのアナウンスはまだなさそう。グロサミに来たMVPからのフィードバック欲しくてとりあえずリリースだけしちゃったとかですかね。ホテル・会場のWi-Fi負荷が…

それか、preview 4の時の告知から内容変わってないから書くことないか?

navigation to decompiled sources

navigation to decompiled sourceとか便利そうではあります。まだ「experimental」がついていますけども。

今まで、DLL で参照しているものは、F12で「定義へ移動」しても、シグネチャ(どのクラスにどういうメソッドが、どういう引数であるか)しかわかりませんでしたけども。 それが、逆コンパイル処理をした C# コードを見れるようになるというもの。

設定は以下の場所から。

navigation to decompiled souces の有効化設定

まあ、experimental なせいか、根本的に難しいのか、いまいちなコードが出てきたりはしますが。 あと、この機能を ON にしちゃうと、今までの F12 の結果と違ってアセンブリ名とパスが出なくなるのが不満だったりしますが。

C# 7.2 fix

C# 的には、今回のリリースでは C# 7.2 のままです。新文法の追加はなし、ということになっています。

が、以前書いた通り、バグ修正が結構入っています。in引数(ref readonly)がらみのやべーやつが一通り治っているのに加えて、 以下の2つも「バグ修正」扱いで追加されていたりします。

  • 参照引数な拡張メソッドが、ref thisの語順でもthis refの語順でもよくなった
    • 以前はref thisしか受け付けなかった
  • M(T x)M(in T x)というように、in違いのオーバーロードがあるとき、M(x)だけだと前者を呼ぶようになった
    • 以前はオーバーロードが解決できない扱いでコンパイル エラー
    • 後者を呼びたければM(in x)と書く

ちなみに、「ref partial structpartial ref structの語順も緩めよう」という話もあったんですが、これは今回のリリースには入らなかったみたいです。

.NET Core 2.1 の JIT 最適化の話

$
0
0

唐突ですが問題

とある構造体、例えば以下のようなものがあったとします。

struct X : IDisposable
{
    public bool IsDisposed;
    void IDisposable.Dispose() => IsDisposed = true;
}

この構造体 XDispose メソッドを呼び出すにあたって、 以下の3つのうち、一番高速なのはどれでしょう。

    // (1) インターフェイス引数で受け取って呼ぶ
    public static void Interface(IDisposable x) => x.Dispose();

    // (2) X のまま受け取って、メソッド内でインターフェイスにキャストして呼ぶ
    public static void NonGeneric(X x) => ((IDisposable)x).Dispose();

    // (3) ジェネリックなメソッドで受け取って呼ぶ
    public static void Generic<T>(T x) where T : IDisposable => x.Dispose();

解答

基本的に、というか、これまでは、(3) が最速です。

(1) は、構造体のインスタンスを引数に渡そうとした時点でボックス化が発生。 ヒープ確保と値のコピーが発生するので、結構な遅さになります。

(2) は、結局((IDisposable)x)と書いた時点でボックス化が発生するので、(1)と大差ない速度になっていました。 過去形なのは、.NET Core 2.1でこいつに関する最適化が入ったみたいで、これが実は今日の本題。

(3) だと、ボックス化は発生しません。 .NET のジェネリクスは構造体に対しては型1つ1つに対してそれぞれメソッドを展開するような最適化を行います。 そんな大変なことをやる設計にしたのは、こういうボックス化を避けるためです。 大変なことした甲斐あって、(1)の数倍高速です。 (手元で取ったベンチマークだと6倍くらい高速。)

ベンチマークはGistに置いてあります:

devirtualize最適化

すでに少し触れていますが、この(2)が、.NET Core 2.1で実行すると(3)と同程度の速度が出るようになったみたいです。

要するに、ジェネリックなメソッドの時にやっているのと同じような最適化を、単に((IDispose)x)と書いた時にも行うようになったみたいです。

devirtualize (脱・仮想化)ってのは、要は、「Disposeは仮想メソッドだから本来は仮想呼び出しされるべき。それを、直接的な呼び出しに戻す」と言うような感じ。 構造体に対してこれが効くと、単に仮想呼び出しのコストがなくなるだけじゃなくて、 ボックス化自体が消滅する(結果、ヒープ確保がなくなる)ので、相当効果の大きい最適化になります。

また「ref」を忘れてる…

Merge 日時を見ての通り、まあ、結構前からだったみたいなんですけども。 気付いたのは、以下のプルリクを見て。

どうも、値渡しの時にしか上記の最適化が効いていなくて、「参照渡しでも同じこと効くようにしないとダメだろ」っていう不具合報告が入ったみたいでして。

    // これは devirtualize される(去年の10月から)
    public static void NonGeneric(X x) => ((IDisposable)x).Dispose();
    // これは devirtualize し忘れてた(今出てるプルリクで治る)
    public static void NonGeneric(ref X x) => ((IDisposable)x).Dispose();

ref がらみ、 「動作確認が漏れてて後から気付いて修正」みたいなのかなり多いんですよねぇ…

そういうとこだぞ

ジェネリックな場合に対してこの最適化が掛かっていたのを見ての通り、 この devirtualize っていう手法は大昔から知られている最適化です。 が、まあ、.NET はなんか「JITが頑張らなくてもジェネリクス使えば速いよ?」(実際、ちゃんと書けばほんとに速い)みたいなところがありまして。 JIT は案外さぼってることが結構ありました。

.NET Core 2.1 だと、JIT でも頑張ろうという感じの動きが結構見られます。

もちろんその分、JIT 自体が遅くなったりはするんですが… そこは、.NET にも Tiered JIT (Java でいうHotSpotみたいな、段階最適化)を導入することにしたみたいです。

ちなみに、JIT が頑張れば必ず速くなるってものでもないんですけども。 Escape Analysisみたいに、「.NET だと構造体を使うのが普通で、JIT が頑張る余地が大して残らない」、「頑張った分のコストでかえって損」的なこともあり得たりします。

ピックアップRoslyn 3/21: Design Notes一斉アップロード祭り

$
0
0

昨日なんですけども、2018年に入ってからのC# Language Design Meetingの議事録(design notes)が一斉にアップロードされました。

読むの大変だった… 春分の日でよかった…

一通りなんとなくは目を通したんですけど、ブログ1回の内容じゃなさすぎるので、少しずつネタにしていこうかと。

ここ数時の状況

2週間前にVisual Studio 15.6が正式リリースされて、 その後ほどなくして15.7のプレビュー1もリリースされたわけですけども。

このプレビュー1の時点では 15.7 に C# 7.3 は入っていなかったわけですけども、 roslynリポジトリの15.7マイルストーンを見るとだいぶC# 7.3がらみの作業がマージされている状況です。 作業進捗を表すLanguage Feature Statusのページもつい5日前に更新されて、C# 7.3のところの大半の機能が Merged になりました。

要するに、15.7向けのC# 7.3の最低限の作業が完了したんでしょうね。 あとは正式リリースに向けてバグ出し・バグ修正するフェーズに。 おそらく近々15.7プレビューにC# 7.3対応が来るのではないかと思われます。

ちなみに、roslynのナイトリービルドに挙がっているVSIXやNuGetパッケージをインストールすれば、結構ちゃんとC# 7.3が使えていました

そして、作業が落ち着いたタイミングで毎度やってくる「一斉投稿」が昨日来たと…

最近採用が決まった提案

とりあえず今日はこの話題のみ。 3/19のDesign Noteで今後取り組む作業の選別をしたようで、 いろんな提案issueが新たにChampioned(将来取り組むこと自体は決定)に昇格しています。

取り組み時期はたいてい「8.X」。「8.0ですらなくさらにその後」という意味で、実際のところ「未定」と大差ないやつです。 そんな状態のものなので、具体的な文法はこれからまだだいぶ変わると思います。

default in deconstruction

↓みたいな書き方を認めてほしいというもの。

int x;
int y;
(x, y) = default; // x = default; y = default; と同じ意味

and, or, and not パターン

↓みたいに、パターン マッチングで条件のところに and, or, not を書けるようにしたいとのこと。

switch (o)
{
    case 1 or 2:
    case Point(0, 0) or null:
    case Point(var x, var y) and var p:
    case not string _:
}

型引数の部分的な型推論

いくつか文法案は出ているものの、そのうちの1つで書くと、↓みたいな感じ。

M<int, >(args); // 2個目の型引数だけは args から推論できて、1個目は無理な時、こう書けるようにしたい

制約なしの型引数に対して is null を認めたい

ちょっと説明しにくいんですけど、以下のような感じ。where T : classなしのT tに対して、t is nullを認めたい。

void M(string s)
{
    if (s is null) { } // OK。クラスだし、null チェックしたい
}

void M(int x)
{
    if (x == null) { } // 警告は出るけど別にエラーにはならない。常にfalse
    // ↑あんまり良い話ではないけど、default とかジェネリクスがなかった頃の名残っぽい
}

void M1<T>(T t)
    where T : class
{
    if (t is null) { } // OK。クラス制約あるし。
}

void M2<T>(T t)
{
    if (t is null) { } // 今は NG。
    // とはいえ、構造体の == null が OK なんだから別にこれも認めていいでしょ。常にfalseで
}

暗黙的なスコープのusingステートメント

以下のような、usingしたいリソースがたくさんあるときのネスト問題への対処。

using (var d = SomeDisposable())
{
    // ここのネストが1段深くなるのがしんどい時がある
}

// 特に、多段の時。最後の1個以外は {} を省略できるとはいえ
using (var d1 = SomeDisposable())
using (var d2 = SomeDisposable())
using (var d3 = SomeDisposable())
using (var d4 = SomeDisposable())
using (var d5 = SomeDisposable())
{
}

以下のような書き方を予定。

{
    // 変数宣言の前に using を付けることで、その変数のスコープを using のスコープにする
    using var d1 = SomeDisposable();
    using var d2 = SomeDisposable();
    using var d3 = SomeDisposable();
    using var d4 = SomeDisposable();
    using var d5 = SomeDisposable();

    // Dispose が走るのは、変数がスコープを抜ける時
    // = このブロックから抜けるとき
}

defer ステートメント

Swift にあるやつ。

static void Main()
{
    defer
    {
        Console.WriteLine("関数を抜ける時に呼ばれる"); // 例外があっても常に
    }

    Console.WriteLine("こっちの方が先に表示される");
}

「一回り外側のブロックに影響する」っていう点が気持ち悪くて据え置きになっていたんですが… 前節のusing varを認めてしまった以上、それを理由にリジェクトできなくなった感じ。

前節のusing varを使って、以下のように代用できないこともないんですが、 これだとラムダ式のオーバーヘッド(デリゲートのヒープ確保とインライン展開の阻害)が掛かるのが嫌だそうです。

    using var d = new ActionDisposable(() =>
    {
        Console.WriteLine("関数を抜ける時に呼ばれる");
    });

    Console.WriteLine("こっちの方が先に表示される");

ユーザー定義の位置指定パターン(positional patterns)

パターン マッチング、C# 7.0時点では「型パターン」とか「定数パターン」とか、一部分だけが実装されました。 残りの大部分は、現状、C# 8.0で提供する予定になっています。

そんな中、さらに C# 8.0からも外れて「8.X」にしようという風に外されたのがこいつ。

struct Cartesian
{
    public double X;
    public double Y;
    public Cartesian(double x, double y) => (X, Y) = (x, y);

    // こいつを使った positional パターンは C# 8.0 で入る予定
    public void Deconstruct(out double x, out double y) => (x, y) = (X, Y);
}

class Polar
{
    // こんな感じの定義を書くことで、Cartesian p を p is Polar(var r, var t) みたいなパターンに掛けることができる仕様がある
    // が、こいつは C# 8.0 では入らない
    public static bool operator is(Cartesian p, out double radius, out double theta)
    {
        radius = Math.Sqrt(p.X * p.X + p.Y * p.Y);
        theta = Math.Atan2(p.Y, p.X);
    }
}

ピックアップRoslyn 3/23: no-allocation非同期メソッド、最近の Utf8String

$
0
0

昨日のDesign Notes祭りとはまた別件なんですが、こんな提案が。

メソッド単位でAsyncMethodBuilder属性

corefx/coreclr方面でガッチガチのパフォーマンス改善をやりまくってる人からの提案。

タイトルからは内容がちょっとわかりにくいんですが、非同期メソッドの際に必要になるヒープ確保量を0 (no-allocation)にするためにこの機能が必要とのこと。

背景としては、

  • C# 6.0: 非同期メソッドの戻り値は常にTaskTask のヒープ確保が必須でつらい → C# 7.0: ValueTask<T>を認めた
  • ValueTask<T>、同期で終わるときにはヒープ確保0だけど、実際に非同期だとどうしてもTaskが発生 → 最近Mergeされたコミット: IValueTaskSourceってインターフェイスを使うことで、Taskを介さず非同期処理できるようにしたよ。うまく使えばTask分のヒープ確保がなくなる
  • IValueTaskSourceで、await毎のヒープ確保はなくせるけども、AsyncStateMachine の1インスタンスはどうしても残った → そのインスタンスをキャッシュして持ちたい

という感じ。

最終的に、ヒープ確保せざるを得ないインスタンスをキャッシュして、プログラム中で1個だけ(メソッド呼び出しのたびには新規ヒープ確保しない)とかにしたいわけですけども、キャッシュはうまくやらないと効率が悪くて困ることになります。

なので、メソッド単位でどういう非同期メソッド生成するかをカスタマイズできたり、AsyncStateMachine にthisを渡せたりしないとこれ以上の最適化が厳しいみたいで、やっと提案のタイトルにつながります。

AsyncMethodBuilder属性をメソッド単位で指定して、メソッドごとに非同期メソッドの挙動をカスタマイズできるようにしたいっていう話は昔からあったんですけども、需要が低いということで実装されずにいたんですけども。 こういう方面から改めて需要が出てくるとは…

UTF8文字列がらみ

去年の9月くらいにGo (Unicode Code Point のことを rune って呼んでる)に倣って Rune 型を作らない?みたいな提案が立っていましたが。 Rune って名前の評判悪すぎて、まあ、別の名前に落ち着きそう。

あと、C# のキーワードも用意したいみたいです。

C# キーワード 実際の型名 サイズ(Byte) 概要
ubyte System.CodeUnit 1 UTF-8エンコーディングされてる文字列の1バイト1バイト
uchar System.CodePoint 4 Unicode のコードポイント
ustring System.Utf8String 内部的に UTF-8 でデータを持つ文字列クラス
uspan System.Utf8Span ReadOnlySpan<ubyte>で、ustringの一定区間を参照する構造体

まあまだ正式な API の提案になってる段階ではないので、まだ変わると思いますが。

ピックアップRoslyn 3/24: Ranges

$
0
0

こないだのDesign Notesアップロード祭り、1月辺りの議題としては割と「Ranges」の話が多かったみたいです。今日はこれの話。

Ranges

レンジ、要するに「どこからどこまで」みたいな値の範囲のことです。そこそこいろんなプログラミング言語にありますけども、例えば 1..3みたいな書き方で1~3の範囲を表そうという構文。

.NET Core 2.1で、配列などの連続したメモリ領域の一定区間を指し示すSpan<T>構造体っていう型が入りました。これに伴い、以下のように、配列の一部分を抜き出すような構文が欲しいという話になっています。

int[] array = new[] { 1, 2, 3, 4, 5, 6, 7 };
Span<int> span = array[1..3];

ちなみに、現状、この機能は C# 8.0 での実現を目指しています。

indexing/slicing 用途

C# チームとしては、前節のような書き方、つまり、[] の中身として使って(= indexing)、配列の一部分を切り出す(= slicing)のを当面の目標に据えたいそうです。

例えば他の用途として、「Range パターン」、要するに、パターン マッチングのパターンの種類として以下のような書き方を認めたいという話もありますが、これはindexingの「次の段階」として考えたい様子。

switch (n)
{
    case 1..3:
    case 5..8:
        // ...
}

「次の段階」としては、おそらく「ユーザー定義の Range 演算子」を認めて、任意の型に対して .. なりなんなりの Range を作れるようにすることになると思います。 しかし、まずは indexing を中心として構文を検討していきたいとのこと。

inclusive/exclusive

先ほど「1..3で1~3の範囲を表す」と言いましたが、1点不明瞭なまま説明を端折った点があります。 両端の数字、この例の場合だと1と3は範囲に含むのか(inclusive)、含まないのか(exclusive)です。

数学だとこれらを明確に区別する記法があったりします。(a, b)みたいに丸括弧を使うとexclusive、[a, b]みたいに四角括弧を使うとinclusive。 組み合わせもあって、(a, b]ならaは含まずbは含む、[a, b)ならaは含んでbは含まない、となります。

inclusive/exclusive

整数だけだと、「1~3 (3は含まない)」と「1~2 (2を含む)」みたいに、±1すれば同じ意味のRangeを作れます。 しかし、将来、ユーザー定義 Range を認めたいわけで、浮動小数点数とかのことも考えないといけません。 浮動小数点数だと「1~3 (3は含まない)」と「1~2 (2を含む)」の意味が全然違うものになります。

他のプログラミング言語だと、..<なら含まない、..=なら含む、見たいに、何らかの弁別のための記号を用意していたりします。 毎度..<とか書くのも面倒なので、「よく使う方」と思うものを単に..と書いて、そうでないものに..=みたいな3文字の記号を割り当てたりもします。

あと、気を付けないといけないのが、言語構文的にinclusive/exclusiveの両方を用意したとして、内部表現はどうすべきか。 例えば、「どちらの構文で書くにしても、内部的には最小値をinclusive、最大値をexclusiveで持つ」と決めたとしましょう。

struct Range
{
    public int MinInclusive;
    public int MaxExclusive;

    public Range(int minInclusive, int maxExclusive)
    {
        MinInclusive = minInclusive;
        MaxExclusive = maxExclusive;
    }
}
構文は仮。C#がこれを採用するとは限らない
var inclusive = 1..=3; // new Range(1, 3) の意味
var exclusive = 1..<3; // new Range(1, 2) の意味

このとき、じゃあ、「0~intの最大値(inclusive)」は表せなくなります。

var inclusive = 1..=int.MaxValue;
// new Range(1, int.MaxValue + 1) の意味
// = オーバーフローして new Range(1, int.MinValue) になる

Swift ではこの問題の対処として、inclusiveなRangesとexclusiveなRangesを別の型にしたんでしたっけ。それも後からの、破壊的変更で。

indexing 用途に絞って

何にしても、とりあえず、用途は広く考えるほどはまりどころは増えます。 C# チームが「まずは indexing 用途に絞って検討したい」としているのもそのためです。 では、その indexing 用途だとどうか。

まあ、この用途だと大半は「最小値がinclusive、最大値がexclusive」です。

// 配列の列挙でよく書くのは < Length
// 要するに最大値は exclusive
// inclusive だと Length - 1 になってちょっと面倒
for (int i = 0; i < array.Length; i++)
{
}

var r = new Random();
r.Next(0, 100); // こう書いた場合 100 は含まない

// この 100 も exclusive
Parallel.For(0, 100, i =>
{
});

なので、おそらく、C# で単にa..bと書くと、「a~b ただしaは含んでbは含まない」になると思われます。 最大値もinclusiveな方は、例えば..:とか.:が使えるかもしれません。

ちなみに、indexing 用途であれば、「負のインデックス」は考えないでいいかもしれません。 そうなると、例えば先ほど言った「inclusiveかexclusiveかを型を変えるなどして弁別できないといけない」とか、 後述する unbounded や starting/length などの表現も、1つの型で、負数の領域をフラグ的に使って弁別するとかできるかもしれません (別途フラグ用のフィールドを持たないでいいので、構造体がコンパクトになるというメリットあり)。

: (コロン)の利用

.: みたいなのを使う上で、.:も別の文法と混ざりやすくて、コンパイラーが区別できる構文が限られたりします。

  • ..1 みたいな書き方で 0.1 を表せる
  • : → 条件演算子 ?:、名前付き引数 引数名: 値文字列補完の書式 {値:書式}

例えば flag ? 1:.2:.3とかは、flag ? (1:.2) : (.3)flag ? (1) : (.2:.3) で不明瞭になったりします。 .: の 方は行けそうなんですが、:. がダメそうという。 同様の理由で、.<は行けても >. はダメそう。

この辺り、Swift の「単項演算子は +x みたいに演算子と変数が密着してないとダメ」「2項演算子は x + y みたいに演算子と変数の間にスペースが必須」みたいな文法は結構良い割り切りだったかもしれないです。

他に、C# は::っていう記号も既存の文法で使っちゃってるんですが、外部エイリアスっていうめったに使わない機能です。 これの両辺は今のところ名前空間しか出てこないはずなので、Rangesとはセマンティック的に弁別できそうとのこと。

starting/length

配列などの一部分を切り出すとき、両端を指定して切り出す他に、「a番目の要素(starting index)からb要素(length)切り出す」という方法も考えられます。

var sub = "abcdefghijklmn".Substring(2, 4); // 2番目から4文字 = cdef

byte[] buffer = new byte[10];
Stream s = File.OpenRead("a.txt");
s.Read(buffer, 2, 4); // buffer の2番目から4バイトに書き込み

ということで、min/max 版の Range の他に、starting/length 版の Range も欲しいという話になります。 C# チームが例に挙げたのだと以下のような構文がありました。

var r1 = 2..4; // min/max (exlusive) = 2, 3 の意味
var r2 = 2..+4; // stating/length = 2, 3, 4, 5 の意味

これだと、ユーザー定義の..演算子を認めたときに、「24に対する..」と「2+4に対する..」で意味が違うってことになるのでかなり気持ち悪い構文になりますが… まあ、何にしても、何らかの構文は求められています。

空 Range

長さ0の Range の扱いはどうあるべきかという話も。

例えば、Range と似たような話で、LINQ の .Skip(a).Take(b) を考えてみます。 「a 番目から長さ b」という意味では前節の starting/length 版の Range と似たようなものになります。 これで、長さ0のシーケンスを2つ比較すると、常に一致扱いになります。

var array = new[] { 1, 2, 3, 4, 5 }; // 値は何でも

// 開始地点は違うけども、長さ 0 のシーケンスを作る
// それを比較すると、開始地点によらず常に一致扱い
var e = Enumerable.SequenceEqual(
    array.Skip(1).Take(0),
    array.Skip(2).Take(0));

であれば、1..+02..+0は同じと思うべきなのかどうか。 これに対しては、一応以下のように、開始インデックスの範囲チェックは掛かるべきなので、別扱いすべきだろうとのことです。

var array = new[] { 1, 2, 3, 4, 5 }; // 値は何でも

var r1 = array[1..0]; // OK
var r2 = array[10..0]; // OutOfRange

bounded/unbounded

indexing 用途を考えた場合、具体的な数値を伴ったa..b以外に、以下のようなものが欲しくなったりします。

  • ..a → 最初から a 番目まで
  • a.. → a 番目から最後まで
  • .. → 最初から最後まで

端の値が決まっている(bounded: 有界)か、決まっていない(unbounded)か。 例えば、行列とかテンソルとかのデータ構造から「1次元目は全て、2次元目は1~3行目を切り出したい」というとき、 matrix[.., 1..3] というような書き方が考えられます。

末尾から n 番目

indexing用途で他によくあるのが、「末尾から n 番目」、「配列長 - n 個」の類です。これに対しても、仮に、以下のような構文を挙げています。

  • 案1: -nみたいにマイナスを付けて「末尾から n 番目」を表す
  • 案2: ^nみたいに、インデックス専用の新演算子を用意して「末尾から n 番目」を表す
  • +^n みたいに、さらに + を付けると「配列長 - n 個」の意味

例えば、「最初と最後の1要素ずつ削る」みたいなことをしたければarray[1..-1]でどうか、と言うことになります。

Index 型

前節で^nみたいな「インデックス用演算子」を考えたわけですが、であれば、Range 型(両端を持つ)だけじゃなくて、いっそ Index 型(1要素だけを指すためのインデックス)もあってもいいのではないかという話になります。

例えば、array[^1]で、末尾から1番目、すなわちarray[array.Length - 1]を表そうということです。 そして、ここまで話してきた「indexing 用途の Range 型」はあくまで、「Index 型の2つの値に対して .. 演算子を適用したもの」ということになります。

natural type

1..3とか書いた時、これの「自然な型」は考えるべきかどうか。

例えば、C#にはいくつか、「左辺の型によって意味が変わる構文」というものがいくつかあります。

// ラムダ式: デリゲートと式ツリー
Func<int, bool> f = x => x > 0;
Predicate<int> p = x => x > 0;  // シグネチャが同じでも Func とは別の型
Expression<Func<int, bool>> ex = x => x > 0;

// 文字列補完: string と FormattableString
int a = 1, b = 2;
string s = $"a: {a}, b: {b}";
FormattableString fs = $"a: {a}, b: {b}";

前節で言ったように、indexing用途の Range の場合は、Index 型の組み合わせだと考えた方がいいかもしれません。 とはいえ、(Index)1..(Index)3みたいな書き方はしたくないでしょう。 ラムダ式のように、左辺から決定するのもありかもしれません。

Range indexingRange = 1..3; // indexing用
IntRange intRange = 1..3; // 「末尾から」とかみたいな妙な仕組みを持ってない単なる整数の範囲

とはいえ、ラムダ式に対してよくある不満として、varやジェネリクスでの型推論が効かないという問題もあります。

// エラー。ラムダ式は左辺の型がないと型が決まらない
var f = x => x > 0;

// こっちはOK。特に指定がない場合、自動的に string 扱い
var s = $"a: {a}, b: {b}";

// Range はどうあるべき?暗黙的に indexing 用の Range 型?
var r = 1..3;

ということで、Range はどうあるべきでしょう。 ラムダ式の例を見ての通り、自動的に特定の「Range 型」に推論される(1..3 にとって自然な型はRange型であるとする)方が使い勝手はいいです。 一方、それをやってしまうと、その特定の Range型に C# が依存してしまうことになります。

まとめ

「a~bまでの範囲」みたいなのを表すRange型は、結構いろんな言語にあります。 なのでそんなに迷うものでもないかと思ったら、案外検討事項がたくさん出ています。

  • inclusive/exclusive 問題
  • unbound な Range
  • max/min 版と starting/length 版
  • 空 Range の扱い
  • 「末尾から」
  • Index 型
  • 演算子を何にするか(特に inclusive/exclusive の弁別のために複数の記号が必要)
  • ある特定の Range型を「自然な型」として採用すべきか

今のところは indexing 用途に絞って、a..b と書いて「a要素目(inclusive)からb要素目(exclusive)まで」の意味で使うというような構文になる可能性がたかいです。 将来的には任意の用途・任意の型に対するユーザー定義 Range が提供される予定です。

現状、C# 8.0での実装を目標にしているみたいですが、 検討が始まったの割と最近ですし、この通りの検討項目の多さなので、もしかするともっと時間が掛かるかもしれません。


ピックアップRoslyn 3/31: C# 7.3

$
0
0

C# Language Design Notes アップロード祭りの続き。

Notes のうち3・4割くらいは、C# 7.3の機能の「最後の詰め」みたいな感じの検討でした。

C# 7.3 の状況

ちなみに、先日のブログでも書きましたが、C# 7.3に含める機能はもう概ね固まっているみたいです。進捗状況を見ると全項目Merged。

(大半は、nightly buildのパッケージに反映済み。まだなのは「Custom fixed」と「Indexing movable fixed buffers」の2つ。)

うちのC# によるプログラミング入門には、Visual Studio 15.7 Previewで拡張なしで C# 7.3を使えるようになる頃にはページを追加したいなぁとか思いながら、nightly build版でちらほら試しに触ってみています。 とりあえずは、今のnightly buildに含まれていない上記2機能を除いて、書きたい内容のサンプル コードを整備:

Design Notes で検討されていたこと

とりあえず具体的な機能説明はC# によるプログラミング入門の方にページ追加するときに頑張ります。

今日は、先日上がった Design Notes で触れられていた内容を軽く紹介。

式中の変数宣言

C# 7.0でいわゆる「out var」が入って、変数宣言を式中でできるようになりました。 C# 7.3では、7.0の頃には使えなかったいくつかの場所でこの機能を使えるよう拡充することになっています。

Notes での検討事項は、主にその変数のスコープをどうするか:

  • コンストラクター初期化子: C(int i) : base(out var x) { x } ← この x{} 内で有効
  • フィールド初期化子: その初期化子内でだけ有効
    • int X = M(out var x).N(x);Nのとこでxは有効
    • 続けて int Y = x;X の方で作った x は、Y の方では無効
    • C# スクリプト実行(変数っぽく宣言したものは、暗黙的に見えないクラスのフィールドになってる)の時の挙動とは一貫性ないんだけど
  • クエリ式: 句単位でスコープ切れてる
    • from x in e where M(x, out var y) select y;selectの方のyは無効
    • ラムダ式の挙動に準じてる。 e.Where(x => M(x, out var y)).Select(x => y);Select内のyは無効
    • 句をまたぎたければ let 句を使って

ジェネリクスの型制約

Enum, Delegate

where T : Enumwhere T : Delegate (または where T : MulticastDelegate)が指定できるように。

このEnumDelegateは、それぞれSystem.Enumクラス、System.Delegate クラスのことです。using System;がないとwhere T : Enumとは書けず。

また、キーワードのenumdelegateを使ったwhere T : enumwhere T : delegate は書けません。 将来的にそういうのも検討するかもしれないけども、少なくとも C# 7.3 では実装しません。

ValueType, Array

同じような「.NETのランタイムが特別扱いしてる型」にSystam.ValueTypeSystem.Arrayがありますが、 これらは現状では有意義な用途が見つかっておらず、「もし用途が見つかれば改めて考える」とのこと。

unmanaged

もう1個、こっちは文脈キーワードの追加で、where T : unmanaged という制約が追加されます。 これは要するに、「ポインター化可能な型」。 「unsafe コード限定機能」のとこで説明していますが、 ポインター化しても安全な型にはちょっとした制約があって、 その制約を満たす型を元々 unmanaged 型と呼びます。

これまで、ジェネリック型引数に対して unmanaged かどうかの判定はできなかったんですが、C# 7.3 でこの制約が入ったことで、ジェネリックなポインターを作れるようになります。

unmanaged の条件の1つが「構造体であること」なので、where T : unmanaged はこの時点で暗黙的に where T : struct の意味も含みます。 なので、where T : struct, unmanaged みたいな併用は認めません。

あと、「ポインター化可能かどうか」の判定には参照アセンブリ問題っていう問題があったりするんですが、 unmanaged 制約もまったく同じ問題を起こします。 (といっても、これは C# コンパイラーの問題ではないので C# チームとしてはどうしようもない。)

ちなみに、var の時にもそうだったんですが、「後からの文脈キーワードの追加」なので、互換性維持のために「varと言う名前の型、unmanagedという名前の型がすでに存在するときは、キーワードではなくてその型の方が優先される」という挙動になっています。 意図的にclass unmanaged { } みたいなクラスを作ると、where T : unmanagedT は「この unmanaged クラスの派生型」という扱いになります。

ref reassignment

ref var r に対して、「参照先の振り替え」ができるようになります。 ref var r = ref x; とした後、r = ref y; で振り替え。その後は r = value; とすると、y の中身が書き換わります。

普通の代入と同じく、この r = ref y も式にできます。 例えば、連結リスト操作で while ((l = ref l.Next) != null) みたいなコードを書けます。

あと、C# 7.3 では foreach (ref var x in source) みたいな、foreach の反復変数も参照にできます。 これも、通常の反復変数が書き換え不可なので、参照の場合の「参照先の振り替え」は不可です。

ref int r; みたいに未初期化の状態で変数宣言した後、r = ref x; と後から参照先を割り当てることも考えられますが、C# 7.3 の時点ではやらないみたい。 「安全性チェック」(戻り値として返せるかどうか)が大変になるからとのこと。

stackalloc

C# 7.2 で「安全な stackalloc」が入ります。 そのため、これまでなら「unsafe 限定のレア機能」だったstackallocが、C# 7.2 以降利用頻度が上がる可能性があります。

ということで、stackallocの使い勝手を上げてもいいだろうという話に。 具体的には、配列のnewに対してできることはstackallocにもできてもよいという話。 要するに、stackallocに初期化子を付けれるようになります。 stackalloc[] { 1, 2, 3 }とか。

パターン ベースの fixed ステートメント

Span<T>はパフォーマンス向上のために導入された型で、 こいつに対してポインター操作をしたいという需要は結構高いようです。 しかし、Span<T>からポインター取得する際にfixed (GC によってポインターが指してる先が移動しないように、オブジェクトを「固定」する)をどうやって掛けようかと言うので悩んでいました。

今は、fixed (T* p = MemoryMarshal.GetReference(span))とか書いたり、 ちょっと前まではfixed (T* p = span.DangerousGetPinnableReference())とかだったりしました。 これを、fixed (T* p = span) と書いたら何らかのメソッド呼び出しに置き換えたいという話。

展開結果は、まあ、「Dangerous はねぇわ」という感じみたいで、 結局、今現在 marge されているコードを見るにGetPinnableReferenceと言う名前を使いそう。

stringとかの標準ライブラリ中の型に関してはこのGetPinnableReferenceをインスタンス メソッドとして追加予定。 一方で、(ref thisin thisを含む)拡張メソッドもOKにする予定。

safe な手段ではできないんですが「null 相当する参照」を返すことで、fixed (T* p = s) の結果のpがnullポインターになることも認めるとのこと。 nullチェックは使う側の責任。

タプルの ==, !=

C# 7.3 から (x, y) == (1, 2) みたいな、==!=を使ったタプルの等値判定ができるようになります。

C# のタプルの実体はSystem.ValueTuple型なわけですが、これの==演算子を呼べばいいという話ではないです。 タプルは「メンバーごとの暗黙の型変換」を認めているので、比較でも型変換を考慮する必要があるとのこと。 例えば、(int x, int y)なタプル変数tに対して、t == (123L, 1E10D) とかができます(それぞれlongdoubleとの比較)。

ということで、タプルの==は、「メンバーごとの==呼び出しを&&で繋いだもの」として解釈。 ユーザー定義の==で、boolではなくユーザー定義型を返す場合、それのoperator.trueoperator.falseが呼ばれます。

オーバーロード解決

C# 6.0くらいの頃からずっと「bestest betterness」とかいうふざけた言い方をしてたやつをついに実装するそうで。 ちなみに、betternessルールの改良はたびたびやっているので、 「better betterness」→「best betterness」→「bestest betterness」とかポケモン進化みたいなふざけた名前になりました。

今まで、「まず引数の型の一致を確認」→「制約等を満たすか調べて、満たしてなければコンパイル エラー」という順の処理をしていました。 制約を調べる方があとなので、解決できないオーバーロードが結構あったという。 それを、引数の一致を調べるより先に、以下のものを調べるように変わります。

  • ジェネリック型制約
  • 静的メンバーかインスタンス メンバーか
  • (メソッドをデリゲートに渡すときに)メソッドの戻り値の型

これは bestest だわー。 (さすがにふざけた名前なので、最近、Proposal のタイトルも「Improve overload candidates」に変更されました。)

ピックアップRoslyn 4/4 C# 8.0辺りの検討事項

$
0
0

Design Notes の一斉アップロード祭りがらみ、今日でやっと最後。 先月28日に1つ追加の Note もあります

とりあえずこれまでの分:

で、今日は最後に、C# 8.0 辺りで入りそうな機能の話になります。

Caller Expression Attribute

Caller Info 属性に1種類追加。 M(2 * x, y * y, Sin(z)); みたいなメソッド呼び出しをした時に、2 * x, y * y, Sin(z) の部分をそのまま受け取るというデバッグ用の機能です。 主な用途は Assert 系の API。

で、既存の Assert API がこの機能を活用しようと思ったら、 Equal(T expected, T actual) みたいなメソッドを Equal(T expected, T actual, [CallerExpression]string expression = null) みたいなシグネチャに変える必要があります。

こいつの呼び出し側は、元のまま Assert.Equal(x, y); で呼べばいいんですが…

  • 既存の Equal(T expected, T actual) を残すと、こっちの方が優先度が高いのでこっちしか呼ばれない
  • 既存の Equal(T expected, T actual) を消すと、ソースコード互換はあるけどバイナリ互換はない(一応、破壊的変更)
    • ソースコードの再コンパイルなしで、Assert ライブラリの DLL だけ更新すると実行できなくなる

という問題が。 XUnit は昔この手の引数追加での破壊的変更を1度やっているらしく、まあ、許容できると言えばできそう。

null 許容参照型

このブログでもたびたび紹介していますが、参照型も T (null なし)とT? (null あり)の区別がつくようになるやつ。

待望の機能ですし、すでにプロトタイプ実装も始まっていますが、やっぱ細々と検討事項が出てきます。

明示的なキャストの扱い

(T)x とか (T?)x とか、キャストをどう扱うかというのも案外自明ではないみたい。 特に、既存コードからの移行の都合と、新たに書き始めるのであれば追求したい「正しさ」との間のバランスで悩むとか。

  • 既存コードには (string)null とかで「型指定ありの null」とかは結構書くけども
    • これは自動的にstring?扱いすべきか(既存コード優先)
    • 警告すべきか(正しさ優先)

M((string)x)M((string?)x) みたいなキャストは、ジェネリック メソッドの呼び分けでははっきりとキャストが意味を持ってる。 例えばT M<T>(T x) に対して、 var y = M((string)x) とすると ystring (nullなし)になるし、 var y = M((string?)x) とすると ystring? (nullあり)になる。

一方で、Mが旧時代のコード(TT? の区別をしない時代のコードでは、T から null が帰ってくることがあるし、 コンパイラーもそのつもりで null チェックをする)だった場合、 M((string)x) はどう解釈すべきか。 x が新時代(T?あり)コードなら警告でいいけども、 x も旧時代コードならこのstringはどう扱うべきか。

警告の種類

古いコードをアップグレードするとき、一気にはコードを修正できないので #pragma warning などを使って警告を無視したいことがあり得る。 この「警告オフ」作業を煩雑にしないために、null チェック絡みの警告の種類はまとめられる限りまとめたい。

実際、「null リテラルを T に代入」と、「T? 型変数の値を T に代入」は統合したみたいです。

型推論

T? 型の変数であっても、null チェックがあったらそれ以降は「null ではない」という扱いになります (コンパイラーがそういう風に判断して警告の有無が決まる)。

例えば、以下のような判定になったりします。

string? ns = ...
if (ns is null) return; // null だったらここから下にはいかない
var s = ns; // なので、s は「非 null」

ここで問題なのは、じゃあ、この s は「string?だけどnullチェック済み」という判定なのか、 「string型として推論されてる」という状態なのか。 ジェネリック メソッドに s を渡した時のオーバーロード解決とかにも絡んでくる。

var?

var s1 ="Hello"; とか書いたとき(string扱い?)、s1 に後から null を代入したい場面ではどうするべきか。

var s2 = (string?)"Hello"; は、「varと書くと非nullとして推論」として認めないようにするか、string?として推論すべきか。

varと書くと非null」の対として、var? s3 = "Hello"; みたいな書き方を用意する?

Records

C# 6.0の頃から案だけはあるものの、気が付けば C# 7.X でも入らず 8.0 に伸びてる Record 型。 class Record(int X, int Y); とか書くと、プロパティ XY とか ==Deconstruct を自動で実装してくれるというやつです。

先月の Microsoft MVP Global Summit でやっぱ MVP からさんざん突っ込みが入ったそうで。それに対する回答:

  • 重要性はわかっているけども、現実主義的に行きたい(要するに、課題も多くて完成には及ばず)
  • Discriminated Unionsと併せて取り組みたい。別機能ではあるものの、Records と一緒に取り組むとより良くなる
  • いくつか「用途が狭い」と判定された提案は「コード生成でやってくれ」と言ってきてるが、Records に関して言うとコード生成の領分ではない。ちゃんと言語機能として取り組むべき

Ranges

28日に追加分でも Ranges に関する記述あり。

プロトタイプ提供するにあたって

Ranges 構文はあくまで Range 型を作るという機能であって、Span<T> span = array[1..3]; みたいな書き方をするためには、配列やListRange型を受け付けるインデクサーを持っていないといけない。

で、この機能のプロトタイプを提供するにあたって、こういう「Range型を受け付けるインデクサー」も同時にプロトタイプ提供したい。 特に、配列とかstringとかの型でこれを使いたいわけだけど、そのために .NET Core ランタイム側で対応作業をしてもらう必要があるとなると、 プロジェクト間の依存が大きすぎる。

Span<T> this[Range range] インデクサーを被せるだけのラッパー構造体を作ってそれをプロトタイプ提供すべき?

それか、一時的に「拡張インデクサー」を提供してしまう? (拡張インデクサーは、Shapesって機能の一部として提供したけども、 こいつは 8.0 よりもさらに先で検討されてる。Ranges の方が先に実装される。なので、本来 Ranges と同時期には提供しないはずの機能を「一時的に」「仮実装」で提供。)

今のところ「一時的な拡張インデクサー」の提供を考えてる。

どこまで提供するか

結局どこまでやるか。

  • m..n で「m から n まで」みたいな開始・終了インデックス型だけを提供
  • 「m から len 要素」みたいなやつや、「配列の末尾から n 要素目」みたいなのを考えて、Index型と^演算子みたいなのも提供

前者なら実装は楽だけどできることの制限が強い。 まずはこの「実装が楽な部分」だけを実装してみて、ユーザーの反応を見てみるという手もある。 でも、ちょっとこの制限はきついと思っていて、たぶん高機能な後者の方の需要はある。

switch 式の網羅性

switch 式」では網羅性(exhaustiveness)のチェックをしたいという話がある。 例えば bool なら(変なことをしなければ) truefalse の2値しか持っていないわけだから、 x switch { case true: a; case false: b; } みたいな式は「すべての値を網羅している」と言えるはず。

これをコンパイラーがチェック(網羅されていれば「default 句なし」を認める)すべき? チェックするとして、(いくつか、コンパイラーの静的チェックに漏れるケースがあり得るけど、その時のために)実行時チェックもする?

今のところ、網羅性をチェックして、網羅できていなければ警告にする。 静的チェックに漏れた場合の実行時チェックも入れて、例外を投げるようにすると思う。

nested stackalloc

現状、stackallocは非同期メソッド内では使えないという制限があります。 まあ、await をまたいで使うことは原理的にできない機能ではあります。

async Task M()
{
    Span<int> span = stackalloc int[10];

    await Task.Delay(1);

    span[0] = 1; // これは本当に不正。原理的に無理
}

でも、await さえまたがなければ、例えば以下のような書き方であれば安全に使えるはずです。

async Task M()
{
    {
        Span<int> span = stackalloc int[10];
        span[0] = 1;
    }

    // ブロックでくくったので、span がここに漏れることはない
    await Task.Delay(1);
    // ここから下で使えなければ span は安全
}

この、「ブロックで囲った(nested) stackalloc」を認めたいという感じになっています。

C# にも型クラス(Shapes)が欲しい… 距離空間上のアルゴリズム実装

$
0
0

今日は C# で「距離の計算」を汎用的に、かつ、高パフォーマンスでやりたいという話。 というか、やりたいのはやまやまなんだけど、高パフォーマンスを目指すとなかなか大変なことになるという話。 Shapesが来れば楽になるはずだけども、計画上だいぶ先の話なので、待っていると厳しいので大変なのを我慢したという話でもあります。

サンプル コードの全体像: MetricSpace

距離

2つの何らかの情報の距離を求めたいことは結構あります。

汎用性が要らないなら簡単な話で、以下のようなコードで書けます。

class Euclidean
{
    // a と b の長さが同じとか、いくつか前提を置いちゃってるけども、最低限のコード
    // a, b を N 次元空間上の点とみなして、その間の距離の2乗、
    // 要するに「差の2乗和」を求める。
    public static float DistanceSquared(float[] a, float[] b)
    {
        var d = 0f;
        for (int i = 0; i < a.Length; i++)
        {
            var dif = b[i] - a[i];
            d += dif * dif;
        }
        return d;
    }
}

ここで、汎用性を気にすると以下のような要望が出てきます。

  • 数値の型: float以外にも使いたい
  • 数値の「組」の型: 配列じゃなくしたい
  • 距離計算の方法: 2乗和(いわゆるユークリッド距離 )だけが距離じゃない

数値の型

距離計算に使う演算は、和、差、積程度です。 あと、「同じ点かどうか」は知りたいことが多いので、等値比較はしたいでしょう。 距離を計算したあと、「一定範囲に収まっているかどうか」を判定することが多いので、大小比較くらいは必要です。

とはいえ、floatに限らず、数値型ならどの型でもこの条件くらいは満たします。 実際例えば、「四角いマス目の上の点」みたいなのを考えるとfloatよりもintが使いたくなります。 精度が必要な場合にはdoubledecimalを使いたくなるでしょうし、省メモリ都合でshortを使いたい場合もあるでしょう。

となったときに問題になるのが、C# では、数値の四則演算を素直にジェネリックにできないこと。 以下のコードはコンパイルできません。

// int や double でも使いたいからと言って、以下のようには書けない。
// ジェネリックな型 T には +, -, * が定義されていない。
class Euclidean<T>
{
    public static T DistanceSquared(T[] a, T[] b)
    {
        T d = 0;
        for (int i = 0; i < a.Length; i++)
        {
            var dif = b[i] - a[i];
            d += dif * dif;
        }
        return d;
    }
}

しょうがなく、以下のように書いたりします。

interface IArithmetic<T>
{
    T Zero { get; }
    T Add(T a, T b);
    T Subtract(T a, T b);
    T Multiply(T a, T b);
}

class Euclidean<T>
{
    // 四則演算用のインターフェイスを外からもらう
    IArithmetic<T> _arithmetic;
    public Euclidean(IArithmetic<T> arithmetic) => _arithmetic = arithmetic;


    // static にするのはあきらめる
    public T DistanceSquared(T[] a, T[] b)
    {
        var arith = _arithmetic;
        // IArithmetic<T> 越しに 0 をもらったり、四則演算したり
        var d = arith.Zero;
        for (int i = 0; i < a.Length; i++)
        {
            var dif = arith.Subtract(b[i], a[i]);
            var sq = arith.Multiply(dif, dif);
            d = arith.Add(d, sq);
        }
        return d;
    }
}

が、これだと、

  • IArithmetic<T>Euclidean<T>のインスタンスを持ちまわすのが大変面倒
  • 仮想呼び出しのせいでインライン化が効かなくなってものすごく遅い

という問題があります。

で、ちょっとしたトリックなんですが、値型ジェネリックを使うとインライン化が効くという黒魔術がありまして。 以下のように書けば倍は速くなります。

class Euclidean<T, TArithmetic>
    // 構造体にして、型引数で受け取る
    where TArithmetic : struct, IArithmetic<T>
{
    public static T DistanceSquared(T[] a, T[] b)
    {
        // default を使って IArithmetic<T> を作る
        var arith = default(TArithmetic);
        // あとは先ほどと同じ
        var d = arith.Zero;
        for (int i = 0; i < a.Length; i++)
        {
            var dif = arith.Subtract(b[i], a[i]);
            var sq = arith.Multiply(dif, dif);
            d = arith.Add(d, sq);
        }
        return d;
    }
}

struct FloatArithmetic : IArithmetic<float>
{
    public float Zero => 0;
    public float Add(float a, float b) => a + b;
    public float Multiply(float a, float b) => a - b;
    public float Subtract(float a, float b) => a * b;
}

// IntArithmetic, DoubleArithmetic, ...
// 使いたい型の分だけ同じ IArithmetic<T> を書く

class Program
{
    static void Main()
    {
        // FloatArithmetic の時点で T は float で確定なんだけど、残念ながら型推論はされない
        // 常にこの2つの型引数をペアで渡さないといけない
        Euclidean<float, FloatArithmetic>.DistanceSquared(new[] { 1f, 2f }, new[] { 3f, 4f });
    }
}

一応これで、最初の float 専用で書いたコードに近いパフォーマンスになります。 まあ、面倒も多々あって、特に大変なのが、型引数を常にペアで渡さないと行けなくなる部分です。 この先、さらにどんどん面倒になって行くんですが、もうすでにこの時点で相当面倒です…

数値の「組」の型

次の課題は、配列を避けたいという点。 前述の例でも、メソッド呼び出しの際に new[] { 1f, 2f } とか書いていますが、 配列はヒープを使ってしまうので、 今回のような用途ではパフォーマンス上、あまり好ましくありません。

今回の用途だと、

  • 要素数が常に固定
  • しかも、よく使うのはせいぜい2次元か3次元

ということで、配列の代わりに以下のような構造体を使いたくなったりします。

struct Array1<T>
{
    public T Item1;
}

struct Array2<T>
{
    public T Item1;
    public T Item2;
}

struct Array3<T>
{
    public T Item1;
    public T Item2;
    public T Item3;
}

// 以下、必要なだけ ArrayN を用意

で、以下の理由から、こいつに対しても先ほどと同様の「値型ジェネリックを使ったトリック」が必要になります。

  • 固定長の配列なんだから、長さを静的に取得したい
  • 構造体は、自身のフィールドを ref 戻り値で返せない
struct Array2<T>
{
    public T Item1;
    public T Item2;

    // これをジェネリックに使いたければトリックが必要
    public static int Length => 2;

    // ただでさえ、safe にインデックス アクセスを実現する方法はないんだけど…
    // そもそも、C# の構造体は ref Item1 したものを、ref 戻り値では返せない仕様
    public ref T this[int index] => ref Unsafe.Add<T>(ref Item1, index);
}

その結果、行きつく先は以下のようなコードになります。

// 配列自体用。これは大して意味は持ってない。誤用防止程度
public interface IFixedArray<T> { }

// 値型ジェネリック トリック用
public interface IFixedArrayAccessor<T, TArray>
    where TArray : struct, IFixedArray<T>
{
    TArray New();
    ref T At(ref TArray array, int i);
    int Length { get; }
}

public struct Fixed2<T> : IFixedArrayAccessor<T, Fixed2<T>.Array>
{
    public struct Array : IFixedArray<T>
    {
        public T Item1; public T Item2;
        public Array(T item1, T item2) => (Item1, Item2) = (item1, item2);
        public static implicit operator Array((T, T) value) => new Array(value.Item1, value.Item2);
    }

    public Array New() => default;
    public int Length => 2;
    public unsafe Span<T> AsSpan(ref Array array) => new Span<T>(Unsafe.AsPointer(ref array.Item1), 2);
    public ref T At(ref Array array, int i) => ref AsSpan(ref array)[i];
    // 範囲チェックをさぼる(危険でいい)なら以下の書き方でも OK
    //public ref T At(ref Array array, int i) => ref Unsafe.Add(ref array.Item1, i);
}

この時点で結構悩ましいコードですが、されにこれを距離計算に組み込むと以下のようになります。

class Euclidean<T, TArithmetic, TArray, TArrayAccessor>
    where TArithmetic : struct, I/OArithmetic<T>
    where TArray : struct, IFixedArray<T>
    where TArrayAccessor : struct, IFixedArrayAccessor<T, TArray>
{
    public static T DistanceSquared(TArray a, TArray b)
    {
        var arith = default(TArithmetic);
        var accessor = default(TArrayAccessor);
        var d = arith.Zero;
        for (int i = 0; i < accessor.Length; i++)
        {
            var dif = arith.Subtract(accessor.At(ref b, i), accessor.At(ref a, i));
            var sq = arith.Multiply(dif, dif);
            d = arith.Add(d, sq);
        }
        return d;
    }
}

class Program
{
    static void Main()
    {
        // これも、Fixed2<float> を使う時点で残りの型引数確定なんだけど、残念ながら型推論はされない
        // 常にこの4つの型引数が必要
        Euclidean<float, FloatArithmetic, Fixed2<float>.Array, Fixed2<float>>.DistanceSquared((1, 2), (3, 4));
    }
}

型引数が4つに増えました。 しかし、実際のところ意味がある情報は、float、「2次元」の2つだけです。 気持ち的には Euclidean<float, 2> とだけ書きたいですが、C# では叶いません。

距離計算の方法

最後に、距離の計算自体も汎用化してみましょう。

距離にもいろいろあります。 ぶっちゃけ、「非負」「三角不等式が成り立つ」の2点だけ満たしていれば何でも距離です。 ユークリッド距離以外でそこそこよく使うものだと以下のようなものがあります。

  • マンハッタン距離
    • 絶対値の和
    • 京都やマンハッタンの街みたいに碁盤の目になっている都市での2点間の距離
    • 「一定距離にある点」をつなぐと、ダイアモンド型になる
  • チェビシェフ距離
    • 絶対値の最大値
    • チェスや将棋みたいに、斜めにも動ける駒にとっての盤面の距離
    • 「一定距離にある点」をつなぐと、四角になる

これも、汎用化するだけならインターフェイスを1個用意するだけなんですが、 パフォーマンスを考えると値型ジェネリックを使うことになります。 行きつく先が以下のようなコード。

interface IMetric<T, TArray>
    where TArray : struct, IFixedArray<T>
{
    T DistanceSquared(TArray a, TArray b);
}

struct EuclideanMetric<T, TArithmetic, TArray, TArrayAccessor> : IMetric<T, TArray>
    where TArithmetic : struct, IArithmetic<T>
    where TArray : struct, IFixedArray<T>
    where TArrayAccessor : struct, IFixedArrayAccessor<T, TArray>
{
    public T DistanceSquared(TArray a, TArray b)
    {
        var arith = default(TArithmetic);
        var accessor = default(TArrayAccessor);
        var d = arith.Zero;
        for (int i = 0; i < accessor.Length; i++)
        {
            var dif = arith.Subtract(accessor.At(ref b, i), accessor.At(ref a, i));
            var sq = arith.Multiply(dif, dif);
            d = arith.Add(d, sq);
        }
        return d;
    }
}

// Manhattan とか Chebychev とかも同様に作る

class Program
{
    // 近い方の点を求める
    static TArray Nearest<T, TArray, TMetric>(TArray origin, TArray a, TArray b)
        where T : IComparable<T>
        where TArray : struct, IFixedArray<T>
        where TMetric : struct, IMetric<T, TArray>
    {
        var metric = default(TMetric);

        var da = metric.DistanceSquared(origin, a);
        var db = metric.DistanceSquared(origin, b);

        return da.CompareTo(db) <= 0 ? a : b;
    }

    static void Main()
    {
        // 型引数は3つと思いきや、Euclidean がさらに4つ求めるので合計7つ
        // 常にこの7つの型引数が必要
        var n = Nearest<float, Fixed2<float>.Array, EuclideanMetric<float, FloatArithmetic, Fixed2<float>.Array, Fixed2<float>>>(
            (0, 0), (1, 2), (3, 4));

        Console.WriteLine((n.Item1, n.Item2));
    }
}

型引数だけで画面の横幅目いっぱい使うようなメソッドができました… もちろん、意味がある部分はfloat, 2, Euclideanだけで、残りは冗長です。

ごまかし

というようなコードを書くことに最近迫られまして。 (元々公開されていたリポジトリからフォークして、上記のようなトリックを仕込んでパフォーマンス向上する作業をした。) 汎用化を捨てたり、パフォーマンスをあきらめてもよかったんですが。 なんとなくきっちりやっちゃいまして。

最初はしょうがなく7つの冗長な型引数を書いてたんですが、 やっぱりすぐにつらくなって断念。 代わりに、以下のようなごまかしコードを書くことになりました。

// ジェネリックな型を1個用意しておいて、派生で型引数を与えておく
// 数値の型
class FloatPoint : Point<float, FloatArithmetic> { }
class DoublePoint : Point<double, DoubleArithmetic> { }
class IntPoint : Point<int, IntArithmetic> { }
class ShortPoint : Point<short, ShortArithmetic> { }

class Point<T, TArithmetic>
    where T : IComparable<T>
    where TArithmetic : struct, IArithmetic<T>
{
    // 数値の「組」の型
    public class _1 : Dimension<Fixed1<T>.Array, Fixed1<T>> { }
    public class _2 : Dimension<Fixed2<T>.Array, Fixed2<T>> { }
    public class _3 : Dimension<Fixed3<T>.Array, Fixed3<T>> { }
    public class _4 : Dimension<Fixed4<T>.Array, Fixed4<T>> { }

    public class Dimension<TArray, TArrayAccessor>
        where TArray : struct, IFixedArray<T>
        where TArrayAccessor : struct, IFixedArrayAccessor<T, TArray>
    {
        // 距離計算の方法
        public class Euclidean : Metric<EuclideanMetric<T, TArithmetic, TArray, TArrayAccessor>> { }
        public class Manhattan : Metric<ManhattanMetric<T, TArithmetic, TArray, TArrayAccessor>> { }
        public class Chebyshev : Metric<ChebyshevMetric<T, TArithmetic, TArray, TArrayAccessor>> { }

        public class Metric<TMetric>
            where TMetric : struct, IMetric<T, TArray>
        {
            public static TArray Nearest(TArray origin, TArray a, TArray b)
            {
                var metric = default(TMetric);

                var da = metric.DistanceSquared(origin, a);
                var db = metric.DistanceSquared(origin, b);

                return da.CompareTo(db) <= 0 ? a : b;
            }

            // その他、距離空間に対するアルゴリズムをこの中に書く
        }
    }
}

class Program
{
    static void Main()
    {
        // 使う側に関してはだいぶ短く書けた
        var n = FloatPoint._2.Euclidean.Nearest(
            (0, 0), (1, 2), (3, 4));

        Console.WriteLine((n.Item1, n.Item2));
    }
}

一応、使う側のコードはだいぶ短くなり、許容範囲になったかなと思います。 ただ、これはこれで、以下のような問題があって、妥協的です。

  • 使いたい型、固定長配列の次元、距離計算の方法が増えるたびに、この派生クラスも追加しないといけない
  • クラスの中に入っているので、他のアセンブリで型を追加できない

根本解決できるようなプログラミング言語を求めるなら、 ジェネリック型引数の推論をもっと頑張ってもらうとか、floatに対するFloatArithmeticみたいなもののペアリングとかの仕様が必要になります。

型の推論は、かなり頑張っている言語もあって、そういう言語ではもうちょっと手短にコードを書けるんですが、 その代わりにコンパイル時間が指数的に跳ね上がったり、 コンパイル エラーが出たときにエラー メッセージが読めた代物じゃなくなったりという弊害があったりします…

floatに対するFloatArithmeticTArrayに対するTArrayAccessorみたいなものは、ShapeとかType Classとか呼ばれたりするんですが、 こいつは将来的に C# に入りそうな雰囲気があります。

これが来てくれればだいぶこの手の作業は楽になるんですが。 現状は明確なマイルストーンが切られておらず、「7.X はおろか、8.X でも無理」という扱いです。 最短で、9.0 とかで入ると仮定しても2年以上は先でしょうか… なので待ってられないので、しょうがなくこんなコードを書くことに…

Visual Studio 15.7 リリース など

$
0
0

Micorsoft Buildに合わせて、Visual Studioの新バージョンが正式リリースしたみたいですね。その他、.NET/C# 関連をいくつか。

告知ブログ:

正式リリース:

リリース候補版:

  • .NET Core 2.1 RC
    • Go Live (自己責任だけど、もう実運用環境で使ってもいいよ)サポートに
    • Alpine Linux サポート
    • ARM プロセッサ サポート
    • SourceLink

プレビュー版:

ロードマップ公開:

  • .NET Core 3
    • 今年後半にプレビュー公開、正式版は2019年リリース予定

C# 7.3

地味な更新なのでほとんど触れられてませんが、C# 7.3も正式リリースっぽいです。

一応なんとかリリースまでに全機能網羅できてんで、 詳しくは「C# 7.3 の新機能」を参照してください。

「C# 7.2の延長」みたいな機能とか、 相変わらずrefがらみ(一般的なユーザーにはそこまで使われないかも)とか、 Span<T>がらみ(言語機能だけは先にあるけど、Span<T>自体はまだリリース候補版の状態)とかが多いですけども。 以下の2つとかは結構面白いかも。

.NET Core for Desktop

.NET Core 3のロードマップでは、「Windowsデスクトップも .NET Coreの上で動くようにするよ」とのこと。

ちなみに、あくまでWindows限定です。 「WPFとかもクロスプラットフォームにする」的な意味ではなく、 「Windowsでしか動かしようのないものもCore上に載せる」という意味です。

メリットは以下のような感じ。

  • ユーザー視点: Windows限定機能も、.NET Coreのメリットを享受できる
    • side by side アップデート可能
      • 同一マシン内に複数バージョンの .NET Core ランタイムをインストールして、アプリごとに選べる
      • 最新ランタイムの機能を享受しやすい
      • 「最新版でしか動かない」みたいなものは少ないものの、「最新版にするだけで速くなる」みたいなのは常にある
      • 今後は「最新版でしか動かない」もあり得る
    • App-local デプロイができる
      • アプリ自体に依存ライブラリを全部含めてデプロイできる
  • .NET チーム視点: ようやく Core に一本化できそう
    • .NET Frameworkにしかない機能がある限り、.NET Frameworkの開発は止めれない

C# vNext Preview

$
0
0

以下のページで、C# 8.0のプレビュー公開を始めたみたです。

インストール

これまででも、まだ Visual Studio プレビュー版にも組み込まれていないような機能の類も、VSIX (Visual Studio 拡張)や NuGet 参照でコンパイラーだけ差し替えることで使えたりはしました。 roslyn のデイリー ビルドを「パッケージ ソース」にして、Microsoft.Net.Compilers パッケージを参照すれば行けます。

ただ、このやり方だと、IDEのC#エディターの IntelliSense は最新版になりません。 ビルドを実行するとコンパイルは通るんですが、エディター上ではエラーの赤線だらけになります。

で、今回公開されたvNext Previewは、インストールとアンインストール用のスクリプトが入っています。基本的には中身は VSIX なんですが、たくさんの VSIX が入っていて、依存順が複雑だとかでそれぞれ個別に入れるのは無理っぽい感じです。 なので、

  • Visual Studio を全て落とす
  • インストール スクリプト(PowerShell)を実行する
  • もし、Visual Studio のバージョンアップをする際には、一度 vNext Preview をアンインストールしてから

という手順を踏んでほしいとのこと。

少し先の機能

最近だと、Visual Studio 自体が、あるバージョンをリリースしてほとんどすぐに、次のバージョンのプレビューを公開しています。 インストールも割と簡単で、正式リリース版との共存もできます。

そして、C# もそれでプレビューを体験できたりしました。 Visual Studio のリリース周期は最近3~4か月ごとなので、そのくらい先のものであればそんなに苦労することなく試せます。

一方、今回インストール用スクリプトを用意して提供しているのは、 要するに2バージョン以上先での提供予定のものを早めに試してもらいたいということでしょう。 久々のメジャー バージョンアップですし、ちょっと大き目な機能も入る予定です。

C# 8.0 プレビューの現状

とはいえ、こないだ公開された「5月4日ビルド版」では、2個しか C# 8.0 の新機能が入っていなかったりはします。 入っているのは、

  • 再帰パターン
  • ranges

の2つ。

再帰パターン

C# 7.0 でパターン マッチングが入ったわけですが、 C# 7.0 時点では、元々計画に挙がってたうちの一部分(型パターン、型スイッチ)だけが実装されています。

C# 8.0 では、7.0 のときに先送りされた再帰パターンが入る予定です。

例えば以下のようなクラスがあったとして、

class Base { }
class A : Base
{
    public int X { get; set; }
    public int Y { get; set; }
    public A(int x, int y) => (X, Y) = (x, y);
}
class B : Base
{
    public string Name { get; set; }
    public int Value { get; set; }
    public B(string name, int value) => (Name, Value) = (name, value);
    public void Deconstruct(out string name) => name = Name;
    public void Deconstruct(out string name, out int value) => (name, value) = (Name, Value);
}

以下のようなコードなら C# 7.0 でも書けました。

static int M(Base obj)
{
    switch (obj)
    {
        case A a: return a.X * a.Y;
        case B b when b.Name == "one": return b.Value;
        case B b when b.Name == "two": return 2 * b.Value;
        case B b when b.Name == "three": return 3 * b.Value;
        default: throw new IndexOutOfRangeException();
    }
}

C# 8.0 では以下のような、再帰的なパターンが使えるようになります。

static int M(Base obj)
{
    switch (obj)
    {
        case A { X: var x, Y: var y }: return x * y;
        case B ("one") { Value: var v }: return v;
        case B ("two") { Value: var v }: return 2 * v;
        case B ("three") { Value: var v }: return 3 * v;
        default: throw new IndexOutOfRangeException();
    }
}

B("one") みたいな、() の部分は位置指定パターンと言って、Deconstruct メソッドが呼ばれています(「分解」と同じ仕組み)。 残りの {} の部分はプロパティ パターンと言って、プロパティに対する X is var x などに展開されます。

switch 式

また、switch 式も追加されます。 式です。=> の後ろとかにも書けます。 今のところは以下のような構文になる予定。

static int M(Base obj)
    => obj switch
    {
        A { X: var x, Y: var y } => x * y,
        B ("one") { Value: var v } => v,
        B ("two") { Value: var v } => 2 * v,
        B ("three") { Value: var v } => 3 * v,
        _ => throw new IndexOutOfRangeException()
    };

{} パターンで null チェック

ちなみに、プロパティ パターン ({} を使ったパターン)には null チェックが伴うそうです。

string s = null;

// null は型情報を持ってなかったり。たとえ、静的な型が一致していても is は常に false。
if (s is string) Console.WriteLine("ここは絶対通らない");

// is string x みたいな変数宣言を伴ってても同じ。
if (s is string x) Console.WriteLine("ここも通らない");

// が、var パターンは常に true。見た目 is string に似てるけど、結果が違う。
if (s is var  y) Console.WriteLine("ここは通る");

// で、プロパティ パターンを使って、null チェック付きの var に近いことができる。
if (s is { }) Console.WriteLine("ここは通らない");

タプル switch

あと、タプルに対する switch では、() を1重に省略できます。

static int M(int x, int y)
{
    // 本来は、switch ((x, y))
    switch (x, y)
    {
        case (1, 1): return 1;
        case (1, 2): return 2;
        case (2, 1): return 3;
        case (2, 2): return 4;
        default: return 0;
    }
}

ranges

ranges は、1..3 みたいな書き方で「1から3まで(ただし3は含まない)のインデックス」みたいな範囲を表す記法です。 Range構造体とIndex構造体に展開される予定で、 このrange.csみたいな定義が必要です。

using System;

class Program
{
    static void Main()
    {
        var data = new[] { 1, 2, 3, 4, 5, 6, 7, 8 };

        // 1~4番目 → { 2, 3, 4 }
        Write(data[1..4]);
        // ↑は、↓と同じ結果
        Write(data.AsSpan().Slice(1, 4 - 1));

        // 2~(Length - 2)番目 = 最初と最後の2要素を飛ばす → { 3, 4, 5, 6 }
        Write(data[2..^2]);
        // ↑は、↓と同じ結果
        Write(data.AsSpan().Slice(2, (data.Length - 2) - 2));

        // 5~末尾 → { 6, 7, 8 }
        Write(data[5..]);

        // 先頭~3 → { 1, 2, 3 }
        Write(data[..3]);

        // 全体
        Write(data[..]);
    }

    static void Write(Span<int> s)
    {
        foreach (var x in s)
        {
            Console.Write(x);
            Console.Write(" ");
        }
        Console.WriteLine();
    }
}

正直、Slice(start, length) みたいな記法との差が少なすぎて、便利さで言うとそこまで大きくはないんですが。 以下のような要件があるので、それなりに必要性はあります。

  • Slice(x, y) みたいな書き方では、第2引数が「長さ」なのか「終端インデックス」なのかで迷う
  • 「末尾から n 番目」みたいなのはdata.Length - n みたいな書き方が必要でしんどい
  • 特に多次元データの時に data[a..b, c..d, e..f] みたいに書きたい

現時点では、以下の実装はないみたいです。

  1. start, length 型の ranges (「a を始点に長さ b」みたいなやつ)
  2. inclusive ranges (「a~b まで(bも含む)」みたいなやつ)
  3. ユーザー定義の .. 演算子

このうち、C# 8.0 正式リリースまでに入るかもしれないのは1の start, length 型 ranges くらい。 残りは「その先また改めて検討」のはずです。

一時的な「拡張インデクサー」

ちなみに、このプレビューでは、「拡張インデクサー」みたいなものが一時的に入っています (T[]Span<T>stringに対するRange型引数のインデクサーを拡張として追加しています)。 これはほんとに一時的な対処で、C# 8.0でこの文法で「拡張インデクサー」が使えるわけではありません。

正式には、「Type Class」という別提案が出ていて、これ待ちです。 もしかしたらこれも C# 8.0 で入るかも。

Viewing all 482 articles
Browse latest View live