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

ピックアップRoslyn 11/16: Microsoft Connect(); 2017

$
0
0

Connect の1日目がありましたが。

Live Share

今回一番気になったのは、Visual Studio Live Shareですかね。 Visual Studio 2017とVisual Studio Codeで、コーディングやデバッグをリアルタイムに画面共有するコラボツール。

複数のオフィスに分かれるような大企業か、大々的にリモート勤務を推してるような会社でないといきなりは使い道ないでしょうけども。 自分も今務めてるのは1フロアに収まる規模のチームですし、基本的には「開発者はフロアをまたぐと開発に支障が出る」というポリシーなんですけども。 とはいえ、「育休で1年だけリモート」みたいなのは十分あり得るので。

C#

まあ、このサイトとしてはこっちが主役。C# 7.2と、その先の話題。

What's new in C# 7.2

Visual Studio 15.5 Previewが出た時点からC# 7.2は試せるわけで、 「普段から追っていれば」新しい話題も特にないんですけども。

ただ、最近ドキュメントがらみが後手に回りがちな C# チームが、今回はちゃんとConnectに合わせて仕事しましたよ!

null許容参照型

あと、null許容参照型 (参照型でも修飾なしのは「非null」扱いにして、? を付けて初めて nullableにするってやつ)のプレビューがアナウンスされました。

短縮URLまで取って: aka.ms/nullable-preview

(これも、このドキュメント自体は数週前から着々と準備が進んでたんですけど、正式に。)

こちらはだいぶ先を見た話です。 null許容参照型という機能自体、予定としては C# 8.0 でのリリースを目指している機能で、7.2すらプレビューな現状からするとだいぶ先のものの、かなり早い段階のプレビュー版です。 15.5 Preview を入れれば割かし低リスクで試せる C# 7.2 と違って、 別途インストーラーを実行して Visual Studio (の参照しているコンパイラー)を上書きするタイプなので注意が必要です。


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

$
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() { }で全部意味が違うという。

Visual Studio 15.5 Release

$
0
0

正式リリース来ちゃった。

ソリューションのロード時間が半分くらいになってるんで一刻も早く使いたいんですけども…

先日、↓の勉強会の最後の方(31ページ目~)でちょっと話した通り、Unity でちょっと問題が出ていて、職場のPCではアップデートしばらくできないかも…

C# 7.2 リリース

ということで、C# 7.2 もリリース。 C# 7.1 が8月だったので、4か月ほどでのバージョン アップ。

C# 7.1 の時と同様、7.2 を有効にするには LangVersion 指定が必要です。

LangVersion latest

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netcoreapp2.0</TargetFramework>
    <LangVersion>latest</LangVersion>
  </PropertyGroup>

</Project>

うちのサイト、今回はもうちゃんと全機能網羅できているはず。

あと、ref がらみの総まとめ的なページを足したいとかは思ってるんですけども、 まあ、機能それぞれ個別には全部書いているはずです。

C# 7.2 は、refがらみの機能が多く、パフォーマンスに関するアップデートになります。 とりあえず C# コンパイラーとしての作業は今回のアップデートで完了という感じなんですが、 .NET 全体としては、

  • Span<T> を安全に使うための C# 機能が入る(今ここ)
  • Span<T> の正式リリース
  • I/O がらみのライブラリが Span<T> に対応
  • アプリが Span<T> 版のライブラリを使いだす

となって初めてパフォーマンス的な恩恵になるので、まだ1歩目を踏み出したところということで、今後に期待となります。

C# のこの先の話

次は、7.X 系でもう1回小さいリリースをした後、8.0 でメジャー アップデートになりそうな感じでしょうか。 7.1 から 7.2 では4か月の短いリリース サイクルだったわけですけども、 その分やっぱり新機能は細かく分割された感じがあります。 C# 7.3 候補は、7.0 の頃から候補になっているものの優先度が低く設定されていたものや、7.2 の積み残しみたいなものが並んでいます。

一方で、大きい機能はC# 8.0 候補に並んでいます。 元々 8.0 に並んでいたnullable reference typesdefault interface methodに加えて、 パターン マッチングの完全版(再起パターン)も、今は8.0ということになっています。

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

$
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の空実装しろと…

Rune と fast Utf8String

$
0
0

今日はRoslynじゃなくてCoreFx方面の話だし、issueの投稿日時的には9月とかで割かし「今更」な話。

  1. Introducing System.Rune #24093
  2. New UTF8String APIs #1751

Rune

1個目のやつ、要は「Unicode のコードポイントに相当するプリミティブ型が欲しい」的な話です。

文字とは…

プログラミングにおける「文字」に関するおさらい:

文字と言えば

charって何なんだろうね… と路頭に迷った結果が、以下のような感じ。

言語 UTF-8 UTF-16 符号点 書記素
C# (byte) char (uint) (string)
Swift (UInt8) (UInt16) UnicodeScalar Character
Go (uint8) (uint16) rune (string)
Rust (u8) (u16) char (String)

() 付きの物は文字としての型がないので「代わりに使うとしたら」

Java とか C# とか、char がUTF-16を指す言語は「前世代」感を隠せないとしても… Unicode コンソーシアムもこういう用語をちゃんと定めないんもんだから場所によって符号点って呼ばれたりスカラーって呼ばれたり… Rust は Unicode 符号点を指して char とか呼んでるけども、結合文字とか不可視文字とかあるからやっぱ「それがcharか?」と言われると微妙だし…

そ し て Go…

上記の issue を最近始めてちゃんと読んだんですけども、よく見たらこんなことが書いてあるわけですよ。

"Code point" is a bit of a mouthful, so Go introduces a shorter term for the concept: rune. The term appears in the libraries and source code, and means exactly the same as "code point", with one interesting addition.

「コード ポイント」とかちょっと長くて言いにくい。なので、Go ではこの概念に対して短い用語、「ルーン」を導入する。この用語がライブラリ・ソース コード中に現れた場合、完全に「コード ポイント」と同じものを意味する。

(※ 最後の with one interesting additionは、次の段落に書いてある「rune型はint32のエイリアスで、『ルーン定数』も書けるよ」って話の事を指してると思う。)

確かに何というか、今のUnicodeの符号点って、呪術的、儀式的な感じしちゃってるんでルーン呼ばわりもわからなくもないものの…

ルーン文字って、それを文字として使ってた頃は神秘性・呪術性なかったらしいじゃないですか。 (「使わなくなった古代文字とかかっこよくね?」的な中二病で、後世になって神秘性帯びたもの。) ただの文字ですぜ…

Proposal: System.Rune

で、これに倣って、C# にもSystem.Rune型を足したら?と言い出したのが冒頭の1つ目のissue。

言いたいことはわかるし、専用の型は確かに欲しいけども、Runeって名前はちょっと…

ちなみに、このissueはMiguel(Xamarin の偉い人)によるものなわけですけども、 この人は製品名にNuGetizer 3000とかEmbeddinator-4000とか付けようとする人なので… ごめんなさい、Rune もこの人のネーミングセンスだと思って一瞬疑いました… Go が犯人とは(ちなみに、Go より前から Rune という呼び名使う人はいたっぽい)、疑ってごめんなさい。

Rune。ルーン文字のrune。 Unicode 内にも U+16A0(ᚠ) ~ U+16F0(ᛰ) の範囲で存在しているルーン文字の、rune。

これ、最悪、

// ルーン(符号点のこと)がルーン(ルーン文字のこと)かどうか調べる
Rune.IsRune('ᚠ');

が生まれちゃうやつじゃないですか。

符号点を表す型

ちなみに、.NET でこの辺りの文字列処理の新ライブラリは、今ちょうど、corefxlabで参考実装を作っているところです。 (corefx に移ってなくて corefxlab 内で日々変更が入ってるような状態なので、リリースまではまだもうちょっと掛かりそう。)

で、corefxlab 産の Utf8String、1年くらい前まではCodePoint構造体を持ってたんですよねぇ。 それが、今はuint生利用に変わっています。 わざわざ、専用の型を削除。

たぶんなんですけども、パフォーマンスのためですかねぇ。 プリミティブ型を構造体でくるんじゃうと、JIT 結果的に、レジスターを使った最適化が掛かりにくくなるみたいな話も見たことがあり。 (ちゃんと最適化が掛かるようにしたい的な issue だったはずなので、今なら最適化掛かるかも?)

Fast Utf8String

そんなRuneの提案ですが、そのさらにきっかけになった別提案が、2つ目のissue。

個人的にはむしろ、こっちの方にこそ面白そうな話が。

元々、Utf8Stringは以下のような型として提案されてました。

ref struct Utf8String
{
    ReadOnlySpan<byte> _buffer;
}

で、これだと「ヒープ上に持っていけないのが不便」と言われ、以下のように変化。

// stack-only
ref struct Utf8Span
{
    ReadOnlySpan<byte> _buffer;
}

// ヒープに置ける
struct Utf8String
{
    byte[] _buffer;
}

さらに、「構造体嫌だ…」と言われます。 なんせ、ImmutableArrayとかでさんざん苦労しているそうで。 この手の「参照型を1個ラップするのに、アロケーションを減らしたくて構造体にした」みたいな型はいろいろと事故ります。 object とかインターフェイスに渡すときにボックス化でパフォーマンス落としたり、 Interlocked.Exchangeがしづらかったり、 「参照型もどき」な癖にさらにそのnullableが作れてしまったり。

ということで、以下のように変化。

// stack-only
ref struct Utf8Span
{
    ReadOnlySpan<byte> _buffer;
}

// ヒープに置けるというか、最初から参照型
class Utf8String
{
    byte[] _buffer;
}

もちろん、これだと、クラスの中に配列があって、ヒープ確保・間接参照が2段階になります。 パフォーマンス的にはちょっと微妙。 (それでも、前述の構造体であることによる問題よりはマシな感じあり。)

なので、上記提案の中には、

  • いつかはやっぱり string 並みのランタイムによる特殊実装入れたい
  • Utf8String は可変長クラスにしたい

みたいな話も出ました。

現状、「可変長」が許されている型はstringと配列だけで、この2つだけかなり特殊扱いされています。 ユーザー定義で可変長な型が許されていれば、ImmutableArrayとか今回のUtf8Stringでこんなに悩む必要もなく、単にクラスで実装すれば済むわけです。 ユーザー定義は認めないまでも、Utf8Stringくらいは可変長クラスであることを認めろよ、という風な流れに。 (この場合、対応しているランタイムであればそういう「特殊実装で可変長」(fast Utf8String)、 していないランタイムであれば前述の「中身は単に配列」(slow Utf8String)な2つの実装を用意することになります。)

fast Utf8String、かなり欲しい… (「後で最適化するのも考えるから、とりあえず今はクラスにするの認めろよ」的な方便でもあるんで、 実際この最適化版Utf8Stringがいつ実装されるかは未知ですが…)

小ネタ インデックス付き 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メソッドを実装しましたが、ガチガチに最適化するなら、以下のように、構造体で実装してヒープ確保をなくすべきかもしれません。

アナライザーのプロジェクト テンプレートとか dotnet new のテンプレートの話

$
0
0

今日は dotnet new向けのテンプレートを書いてみたという話。

Visual Studio 標準の "Analyzer with Code Fix" テンプレートにちょこっと手を入れたやつです。

背景

自作アナライザー

昔、結構アナライザーを書いてたんですよ。

アナライザーに対する規約ベースのテスト

で、アナライザーって、標準のテンプレートだとテストを書くのが結構面倒なので、 規約ベースで

  • 所定のパスに解析対象のソースコードを置く
  • 別のあるパスに Code Fix 結果のソースコードを置く

みたいな仕組みを作ったことがあります。 それがこれ。

NuGet パッケージにしてあって、パッケージ参照すると標準テンプレートのファイルをいくつか上書きします。

新 NuGet

ConventionCodeFixVerifier を書いたのはもうだいぶ昔でして、その当時は NuGet も csproj の形式も古かった時代です。

NuGet はまだ packages.confing とか使ってた頃。 NuGet はその後、project.json 形式 → csproj 中に PackageReference タグを書くように変化しまして。 packages.config 時代とは、「プロジェクト中のファイルを上書き」みたいなのをやりたいときの挙動が違います。 ということで、ConventionCodeFixVerifier も更新しないと今はちゃんと動かないんですが…

その一方で、NuGet でプロジェクト中のファイルを上書きするのも嫌な運用だなぁとかずっと思っていました。 むしろ、プロジェクト テンプレートを作るべき事案だろうと。

SDK-based csproj

あと、Visual Studio 2017でcsprojの形式も新しくなったじゃないですか。 SDK-based csproj とか呼ぶみたいなんですけども。 csproj の中身がかなりすっきりしてたり、この形式を使った場合だけオンになるビルド機能がちらほらあったり。

なんですけども、つい最近まで、標準の "Analyzer with Code Fix"テンプレートは旧型式のままだったんですよね。 しかも未だに packages.config を使ってるという古臭い作り。

それが嫌すぎて、ここ1年くらいアナライザーを書くのを面倒がって、作りたいものはあったものの放置になったり、 上記の自作アナライザーの更新も止まっていたり。

それが、どうも 15.5 で SDK-based な形式に移行したみたい。 なので僕のやる気も復活した次第です。

そうだ、プロジェクト テンプレート作ろう

とはいえ、前述の通り、NuGet でプロジェクト中のファイルを上書きするやり方はやめたいわけです。 でも、Visual Studio のプロジェクト テンプレートって何か作るのめんどくさくて。

と、迷っていたところで、「dotnet コマンドのやつならマシになってるのでは?」と思って検索した結果、

これを読んでみて、「これなら案外行けそうかも」という気分になったので、こっちで作りました。

作ったもの

という背景の元作ったのが冒頭の「AnalyzerConvention」。

導入方法

以下のコマンドでテンプレートをインストールできます。

dotnet new -i AnalyzerProject.Convention

dotnetコマンドで使えるテンプレート、NuGet パッケージを作ればいいらしく、nuget.orgからのインストールもできます。 -i オプションがその「nuget.org とかからパッケージを取って来てインストールする」というオプション。

ちなみに、パッケージ化してnuget.orgに上げたの物:

これをインストールすれば、その後、以下のコマンドでプロジェクトを生成できます。

dotnet new AnalyzerConvention

内容物

作ったテンプレートは、

  • Visual Studio 15.5 の標準の"Analyzer with Code Fix"テンプレート通りにプロジェクト新規作成
  • それにConventionCodeFixVerifierを上書き
    • ただし、もう V1 (古い規約)対応は切りました
  • テスト プロジェクトを XUnit に移行

というもの。

標準テンプレートがベースなので、「クラス名を全部大文字にする」っていうクソ Code Fix のままです。

おまけ: SourceName

このdotnetコマンドのテンプレート、設定ファイルの"sourceName"のところに書いた文字列を、newしたときのフォルダー名(もしくは-n/--nameオプションで指定した名前)で置換する仕組みみたいです。

このsourceNameを何にしようか迷った結果…

Ạṇạḷỵẓẹṛ

に。

Analyzer に見えますけども、よく見ると全文字、下にドットが付いています。 Unicode の「Latin Extended Additional」辺りに入っている文字。 これなら普通のソースコードと混ざらないだろうと。

「混ざらない」って目的は無事果たしたんですけども、ファイル名とかフォルダー名も Unicode になっちゃってて… nupkgって実体はただのZIPファイルなわけですけども、文字化けしてないかが多少不安… 日本語Windows以外で動かなくなってたりしたらどうしよう…

バグ報告祭り

$
0
0

やっぱリリースするといろいろバグを見つけるよねと言う話 (Visual Studio 15.5がリリースされて以来バグ報告たくさん出してる)。

twitter で見かけたのを代理で報告出したり、 前々から気になってたのを改めて調べてみたらコンパイラーのバグだったり、 リリースまでに直るだろうとか高をくくってたやつがまだ直ってなかったり。

safe stackalloc のインテリセンスが効かない問題

報告先: #23584

Span<T>構造体で受け取れば、safe な文脈で stackallocが使えるんですが、 これがコード補完に出てこないという問題。

stackalloc の補完が出ない

インテリセンスのみの問題で、コンパイルは問題なく通ります。

まあ、stackallocを必要とする人が少ない上に、 この機能を使うにはfast Spanが必須。 これが入ってるのは .NET Core 2.1 (まだプレビュー)ということで、 まず試してみてる人がかなり少なそう。

.NET Core 2.1 の正式リリースまでに直ってればいいって感じなのかなぁとか思いつつ一応報告してみたら、 報告した瞬間に修正してたのでやっぱ報告しないと行けなかった奴でした。

式形式(=>) get/set プロパティが、自動実装と誤認されてる問題

報告先: #23668

以下のようなコードで警告が出るという問題。

// Warning CS0282
// この警告は、partial で複数の場所にフィールドを書くと出る。
// 自動実装プロパティも、自動でフィールドが作られるので partial を分けて書くと警告に。
partial struct X
{
    public int A { get; set; }
}

partial struct X
{
    // こいつは自動実装じゃないのでフィールド作られないはずなんだけど…
    // get =>/set => 型のプロパティは、自動実装プロパティと誤判定するらしい。
    int B { get => A; set => A = value; }
}

職場のコードでずっと前から消えない警告があって気になってたんですけども…

大々的にコード生成で作ってるところなので、実際はもっと複雑な partial の分かれ方になっていて、 何が原因かわからず。 時間が解決する(自分が報告しなくてもそのうち治る)かなぁ?とか思っていたものの、15.5でもまだ残っていたので本腰入れて調べたところ、やっと原因を特定したのがこれです。

結局、どうも、プロパティのget/set=>を使って書くと、自動実装プロパティと誤判定するみたいです。 確か、{}で書いてる時と=>で書いてる時、Roslynの抽象構文木が違うんですよねぇ。 それで誤判定してるんじゃないかと。

これも、報告入れた瞬間に修正入ってました。

リファクタリング機能が明示的実装を考慮していない

報告先: #23672

明示的実装をしていて、インターフェイス越しにしかアクセスできないはずのAddに対して、 「コレクション初期化子を使う」リファクタリングが効いてしまって、 リファクタリング結果がコンパイル エラーになるという問題。

IDE0028が不正な結果を作る

該当のQuick Actionを掛けない限り問題はないやつです。

これはtwitterで見かけたのの代理報告。

類似の報告(コレクション初期化子じゃなくてオブジェクト初期化子のもの)は元々バグ報告が出てたみたいです。 Quick Action のID (IDE0028)で検索を掛けたので見つからず(コレクション初期化子のとオブジェクト初期化子のでIDが別)。 どうも、もうバグ修正のプルリクは出てたみたいです。

in 引数がバグりまくり

報告先: #23636

本日の一番やばいやつ。

in引数がかなりバグりまくり…

これもtwitterで見かけたのの代理報告。 ご本人のブログあり

こいつは、実行時エラーを起こすやつなので、踏むと結構デバッグが面倒な奴です…

in引数には、refと違って

  • 呼び出し側には in を付けるのが必須じゃない
  • リテラルを渡せる
  • 既定値を与えられる

とかいう特徴があるわけですが、この辺りがちょうどバグりまくり。

具体的には、参照で渡さないといけないのに、呼び出し側の命令が「値を読む命令」になってて、 ただの値をポインターとして使ってしまってNullReferenceやらAccessViolationやらExecutionEngineやらの例外が出まくり。

起こる条件も複数あって、以下のようなものなどなど。

この2日くらいで別パターンが出るわ出るわ…

まあ、in引数周りの仕様、 つい最近までどこにもドキュメントがなかったんですよねぇ。 最新の仕様に合わせてドキュメントを修正し始めたのが9日前。 マージされたのが3日前…

うちのサイトは一通りちゃんと網羅してるものの、 「確かプルリク出てたからもう実装されているはず」みたいな調べ方して書いてるものだったり。 普通はドキュメントにないもの試さないですよねぇ。 試されないものはそりゃバグが残る…

バグ報告祭りしてみて

その後の経過など:

  • 分かりやすい再現コードを付けて報告を出したら一瞬で修正入る。
  • 報告 issue に書いたコードがそのままテスト コードに入る
  • リリース直前2週間程度で見つかったバグは普通にバグったままリリースされてる
  • たぶん、15.6プレビューの次のやつでいろいろ直ってるはず

ビルドで使う C# コンパイラーを差し替える

$
0
0

Visual Studio 2017 15.5で、C# コンパイラーのコード生成の仕方がちょっと変わりました。

その余波で起きる問題を回避するために、Visual Studio は 15.5 のまま、C# コンパイラーのバージョンだけを下げる方法について話します。

背景

C# コンパイラーが出力するコードのどこが変わったか

15.5 の世代では、とにかくいろいろパフォーマンス改善を行っています。 .NET Coreのランタイムや標準ライブラリのパフォーマンスも1~2割上がっています。 Visual Studioも、ソリューションのロードにかかる時間が半減していたりします。 C# コンパイラーも、コンパイラーが出力するコードがより速くなるように微妙に調整が入っています。

コンパイラーの出力結果なんですが、今まで int (符号付き)だったところが uint (符号なし)で生成されていたりします。 なんでかというと、以下のようなコード(よくある配列の範囲チェックみたいなやつ)を考えたとき、

0 <= index && index < length

以下のように書き換えた方が比較が1回減って速いから。

(uint)index < length

こういう範囲チェック、ポインター操作をしていると頻出するので、 ポインターの扱いを符号付き整数から符号なし整数に変えたみたいです。

ということで、以下のようなコードを書いた場合、

public unsafe void M(string s)
{
    fixed (char* p = s)
    {
    }
}

これまで、conv.i (sのアドレスを符号付き整数に変換)命令が出ていたところに、 conv.u (同、符号なし整数)命令が出るように変わったりしました。

Unity でこれが問題に

この、「ポインター化の conv.u」が、Unity(ゲーム エンジンの)を即死させます。 起きていることは InvalidCast 例外(符号の有無が違うから)の類なんですけども、 命令解釈のレベルで起きてることなせいか、Unity エディターが無言で落ちます。

まあ、問題が起きてる場所的に、Unity が落ちる条件も限定的ではあります。 以下のような条件。

  • unsafeコードを使っている
  • そのコードを Visual Studio 15.5 でビルドした DLL を、Unity で参照する

自社のプロジェクトでは、まさにこの条件を踏んでしまっていて困っていました。

ダウングレードしにくい・side by side インストールしにくい

ということで、最初は、「Visual Studio のバージョンを上げない」運用をしていたんですけども、 チーム内でうっかりバージョンを上げちゃう事案が発生。 自分もうっかり上げたくなる事案は常に発生中。

で、Visual Studio 2017 (15.0)以来、Visual Studioのインストール手順が簡略化されたんですけども (Lightweight インストーラーって呼ばれています)、 その代わり、バージョン違いの Visual Studio の同時インストール(side by side インストール)がかえってやりにくくなってたりします。

「Professional と Community で別バージョンを入れる」みたいなことは結構楽なんですけども、 「どっちもProで、15.4.5 と 15.5.0 を同時に入れる」みたいなのは、Lightweight インストーラーではできません。

しかも、ダウングレードが大変。 アップグレードは「更新」ボタン1つでできるんですけども、 ダウングレードは一度アンインストールからのインストールし直しが必要。

ということで、「うっかり上げちゃう事案」が結構深刻な問題になりました。

コンパイラーのバージョンだけを下げる

ふと、確か、NuGet パッケージ参照で C# コンパイラーを差し替えれる仕組みがあることを思いだしたので、 それで問題回避をしてみることに。

Microsoft.Net.Compilers パッケージ

以下のパッケージを NuGet 参照すると、そのプロジェクトのビルドは、パッケージ中に含まれている csc で行われるようになります。

例えば、Microsoft.Net.Compilers の 2.4.0 を参照すると C# 7.1 時代のコンパイラーになります。

元々は、ASP.NET で使う用です。 ASP.NET だとサーバー上でコンパイルが行われるので、 開発中とサーバー上でコンパイラーのバージョンが違うと困ります。 なので、Visual Studio を使って開発している最中も、指定のバージョンのコンパイラーが使われるようにするのがこのパッケージ。

パッケージ中には、csc 以下ビルドに必要なバイナリ一式と、 そっちの csc を使う設定が書かれた props ファイル(csproj に対してインポートするヘッダー ファイルみたいなもの)が入っています。

このバージョン 2.4.0 を、unsafe コードを使っているプロジェクトで参照するようにしてみたところ、 Unity が落ちなくなる(DLL の中身を ildasm したところ、conv.i に戻っている)ことは確認済み。

問題点

一応、以下のような問題もあります。

  • C# のバージョン自体を下げているので、これを参照したプロジェクトでは C# 7.2 が使えない
    • 影響が出るのはパッケージを直接参照しているプロジェクトだけです
  • さし変わるのはビルドに使うコンパイラーだけなので、Visual Studio 上ではエラーにならないのに、ビルドしたらエラーが出ることがある
    • C# 7.2 の文法を使ってしまった場合だけです
  • 体感できるほどの差はないものの、微妙にコンパイルが遅いはず
  • C# の言語バージョンと、Microsoft.Net.Compilers パッケージのバージョンが違いすぎてどれがどれかわからない
    • 今回の場合は 2.4.0 で大丈夫です。2.4.0 は C# 7.1 になります

まとめ

  • conv.u (unsigned)
  • 今のVisual Studio
    • ダウングレードしにくい
    • side by side インストールしにくい
  • ビルド時に使う C# コンパイラーを差し替えたければMicrosoft.Net.CompilersパッケージをNuGet参照
    • Visual Studio 上のコード補完の挙動とビルドの挙動が変わるので注意
    • ASP.NET 以外で使う日が来るとは…

コピー禁止(non-copyable)構造体アナライザー

$
0
0

書き換えられる(mutable)構造体を作ると事故る問題を解決するためにアナライザー作りました。

mutable 構造体

一般論としては、構造体を mutable に作ると事故ります。 要するに、「書き換えたつもりが、実は書き換えてたのはコピーであって元の値は書き換わってない」的なやつ。 なので、たいていの場合は「構造体は immutable(書き換え不能)に作れ」という指針になります。

その一方で、まれに、ヒープ確保を避けるために mutable な構造体を作りたい場合があります。 フィールドとしてクラスに埋め込んで使ったり、ローカル変数に確保してref引数でメソッドに渡す想定で作ります。

例えば、corefxlab が作りかけてるResizableArrayとか、neueccさんが作ってるUtf8Json中のJsonWriterとか、 書き込み先の配列(満杯になったら確保しなおす)と現在の書き込み位置だけを持つ小さい型なんですが、 構造体で作られています。 自分が昔作ったのだと、Lazy型のためにアロケーションが発生するのがいやすぎて、これの構造体版を作ったこととか。

もちろん、コピーが発生したら事故ります。 「JsonWriterWriteしたつもりが何も書き込まれていない(コピーに対して書き込んでた)」とかやらかしがちです。

コピーの方を禁止する

mutable 構造体で問題が起きるのはコピーが発生するせいです。 なので、コピーの方を禁止すれば、構造体が mutable でも問題は起こしません。

ということで作ったのがこちら。

本当はずっと昔から作ろうとは思ってたんですが。 「Analyzer with Code Fix のプロジェクト テンプレートが SDK-based csproj になったら本気出す」って思ってたらつい最近になってようやく… 先週のブログに書いたAnalyzer 用の自作プロジェクト テンプレートを作った動機もこれ用。

どういうコードが禁止されるかは、テスト用のコードを見てみてください。サブフォルダーの Source フォルダー以下にある csx の、❌ コメントを入れている行を禁止。 以下のような感じでコンパイル エラーが出ます。

non-copyable error

おまけ: 言語機能化の提案

コピー禁止って、必要となる場面がそんなに多くないわりに、解析するの結構大変なんですよね… 自分が作った NonCopyableAnalyzer も完璧なものではないです。 例えば、ジェネリクスが絡むと誤判定あり。

static void Main()
{
    var x = new NonCopyableStruct();
    var illegal = x; // ちゃんと禁止
    var misjudged = Copy(x); // 本来禁止すべき。でも現実装だと通っちゃう
}

public static T Copy<T>(in T x) => x;

「Non-cobyable 構造体」を言語機能として入れてほしいっていう要望とかも出てたりはするんですけども。 言語に組み込むには上記のような誤判定がつらいかなぁという感じ…

こういう、「コピー禁止」があると、昔ブログに書いた非ガベコレな高効率メモリ管理とかも実現できたりするんですが、 これは誤判定がちょっとでも残るとやばいやつなので、相当難しそう…

ピックアップRoslyn 1/3: Regex パーサー / C# 7.2 fixes

$
0
0

なんかこう、GitHub リポジトリのwatchとかしてると、 だいたいアメリカのホリデーシーズンに合わせて自分まで「もう休み」な気分になってしまい。 要するに、12月20日前後にはもう休み気分で。 代わりに1月は2日から仕事に復帰してる感じの人が多く、休みあけた気分に。

Regex パーサー

事の発端は、「C# に Regex リテラルを入れてくれ」とかいう、まあ、芽がない提案なんですが。 issue 自体はだいぶ前からあるものなんですが、ホリデー前後あたりからなぜか再燃。

「芽がない」ってのは、この手の「C# に別言語を埋め込みたい」系の提案が通った試しがないからでして。 今回も、まあ、C# チームの中の人の1人が、

などなど言語機能としては完全否定。

代わりに、IDE機能としては割かし乗り気だったわけですけども、 「休み中の飛行機の中で暇だったから作った」的なノリで、 Regex パーサーを書いちゃったみたいです。

持っている機能は以下のようなもの。

  • VirtualCharService: "\\1@"\1""\\\u0031" みたいな同じ文字列の別表現みたいなのの差を吸収する層
  • new Regex("") の中身に対して働く正規表現パーサー
  • 正規表現が間違っていたら編集時にエラーを検出する
  • 正規表現の構文を見て色付け

まあ、現状、1開発者が休暇中に個人的に作ってみたって感じなので、今後どうなるかはわかりませんが。

あと、「正規表現が間違っていたらエラーに」の方はMicrosoft.CodeAnalysisだからいいとして、 「構文色付け」の方はMicrosoft.VisualStudio.LanguageServicesなんですよね… Visual Studio Code とか for Mac で動くのか…

raw string リテラル

ということで、結局、特別な言語機能なしで Analyzer を書く感じに収まりそうなんですが。 Regex だけの特別扱いというのは今後も芽はないとして。 とはいえ、汎用な機能として C# に追加したい課題はいくつかあったりはします。

1つは、"raw" string リテラル@付きのやつ(逐語的文字列リテラル)よりもさらに、「そのまんま」な文字列リテラルが欲しいという話。 文字列中に \ が頻出することは Regex に限らず結構ありますし。 @"" でも、文字列中の" がやっぱり@"""abc"""みたいな感じでかなり嫌ですし。

てことで、C++ 11 の raw stringみたいに、開始・終了のパターンを任意に変えれるようにしたいという話に(これも前々からある提案の再燃)。

コンパイル時処理

.NET の Regex クラスには、正規表現を解析して、内部的に IL コード生成してキャッシュしておくような機能もありまして。 動的コンパイル。 初回はやたらと遅いものの、何度も同じパターンを調べる場合は圧倒的に速くなります。

でもその、動的にやっちゃうがゆえに初回やたら遅いとか、 AOT を見越して動的コンパイルが使えない環境で困るとか、 結構悩ましいものがあります。

てことで、C# コンパイルの途中の処理をフックしてIL生成できるプラグイン機構を追加してほしいなんて話も度々出たりはするんですが。 Regex の中身のコード生成とかかなり複雑そうで、そういう機構があっても結構きつそう…

C# 7.2 fixes

おまけ。

Visual Studio 2017 15.5をリリースして割とすぐにホリデーシーズンに入ってしまい、C# 関連も一気に動きが鈍くなったわけですが。 一応、15.6に関する計画的なドキュメントはちょこっと更新されていたり。

Language Feature Statusに、 「C# 7.2 fixes」ってセクションが増えています。 おそらくこれが 15.6 でのリリースになります。 今出ている 15.6 のプレビュー版には入っていませんが、次のプレビュー辺りからちらほら入り始めるはず。

まあ、名前通り、基本的にはバグ修正です。 先月書いたやべーやつも直るはずです。 で、ちょこっと文法にも追加?が入ります。

前者は、参照渡しの拡張メソッドrefthisの順序とか、 ref構造体refpartialの順序とか、 15.5 の時点では ref thisref partial の順でないといけなかったのを、どういう語順でもよくするという話。

後者は、通常の引数とin引数でオーバーロードを作ってしまった場合(例えば、void M(T x)void M(in T x))、M(T x) の方を呼び出す手段がなかったという問題の解消です。M(value) と呼べば M(T x) を、M(in value) と呼べば M(in T x) を呼ぶようになります。

いずれも、まあ既存コードは壊しませんし、今までできなかったことができるようになるだけなので、 「バグ修正みたいな物」として扱うみたいです。 要するに、 「ref this の語順でないとコンパイル エラーになる 15.5 の頃の挙動をあえて選びたい」みたいなことは 15.6 以降ではできなると思われます。

await って言う単語

$
0
0

C# 5.0で非同期メソッドが導入されてから、 正式リリースを基準にしても5年以上、 最初の発表からだと7年以上経っています。

で、5年経っても、「なんて読むの」「asyncのaとawaitのaは違う」などなどが「定番ネタ」として定期的に出てくるわけですが。 特に、ECMAScript 2017がasync/awaitを導入したり、 Unity 2017がやっとC#のバージョンを6.0に上げれる感じになってきたり、 5年の断絶を経て去年からasync/awaitに触れる人が増えているようです。

5年も離れたら、世代断絶も起こりますよね… そりゃ、「定番ネタ」が改めて増えもしますよね…

ということで、5年くらい前に同じようなことをどこかで書いてるはずなんですけど、改めて。

英単語

えいしんく

まず読み方。

  • async: エイシンク
  • await : アウェイト

ってやつ。async の方が「アシンク」と読まれがち。 ごくまれに await の方を「エイウェイト」って間違う人もいるみたいですけども…

なんていうか、接頭辞の a を「エイ」って読む英単語は珍しいんでよく間違われます。 普通、aをエイと読むのはアクセントのあるところだけなわけですけども、asynchronous のアクセントは syn のところ。 英語のアクセントって「接頭辞を除いて一番前」なことが多く、接頭辞である a にはアクセントが来ない。

これ、たぶん、冠詞の a との弁別のためにこうなっちゃったのかなぁという感じはあります。 a synchronous ~ と、asynchronicity で a の発音同じだと困るだろうと。 日本語で言うと工学と光学、市立と私立みたいなもので。 「ひかりがく」「いちりつ」「わたくしりつ」とか、読み方としてはきもいけど弁別のためにはこう読まざるを得ないことがあるというような類です。

逆にいうと、冠詞を必要としていない日本語にカタカナ語として輸入するなら「アシンク」でも何も問題はなかったりはしますが… 元々がギリシャ語の ασύγχρονη なのでこれの読みだとアシーンクロニーですし。

ちなみに、後述するように async の a と await の a は別由来の別接頭辞なんですけども、 由来が違うから読み方が違うわけじゃないです。 async と同系統の a だと、例えば atom とかがそうですけども、これは英語でもアトムですし。

a- 接頭辞

asynchronous も await も、a + synchronous、a + wait という形の単語なわけですが…

  • a + sync: a(否定) sync(同時)
  • a + wait: a(向かう) wait(待つ)

になります。

async はギリシャ語系で、a は否定接頭辞。同じような構成の単語だと、

  • atom: a(否定) tom(切る) → 「切れない」 → 原子
  • atopy: a(否定) topy(場所、由来) → 「由来がない」 → 特定されていない、奇妙な

とかがあります。 ギリシャ語だから変。英語化するときに un とかに振り替えとけよ…という感じ。

await の方は、古フランス語(フランク語)の agaiter が由来でそれの a はラテン語の ad- 接頭辞から来てるっぽく、 現代英語だと to (~へ向かう)的な意味っぽい。 近いのはたぶん、

  • amaze: a(向かう) maze(迷路、当惑) → 驚く

とか。 類似の ad- まで広げると、

  • adjust: ad(向かう) just(ちょうど) → 「ちょうどよくする」 → 調整する
  • adventure: ad(向かう) venture(投機、大胆) → 「大胆に向かう」 → 冒険

とか。

同じスペルで全然意味が違うわけですが… a- 接頭辞は特にひどいみたいでして、まとめると以下のようなものがあるっぽいです。

  • ギリシャ語の a- 由来 → not。否定
  • ラテン語の ab- 由来 → from, out。「~から離れる」
  • ラテン語の ad- 由来 → to。「~に向かう」
  • フランス語の à 由来 → さらにたどるとラテン語の ad- 由来。on とか at 的な意味も
  • 古英語の ar-, or- 由来 → on, up

由来違いが混ざったのと、 本来あった差が消失しているせいでひどいみたいです。

日本語で言うと、 不遍・普遍、不通・普通とか、 読む/呼ぶ → 読んだ/呼んだ みたいな感じですかね。 不と普は元の中国語だと区別がつくはずが、日本語は発音が少ないので区別できなくなり。 む・ぶは古代日本語だと区別していたはずが、音便変化で同じになってしまったり。

wait と await

wait 自体に動詞用法がある現在、別途 await があるのはちょっと奇妙なものの、 フランク語の時点で gaiter と agaiter 両方あるみたいです。

await の方は古英語っぽく感じるらしく、古臭い・フォーマルな印象になるそうです。 まあこの辺り、「待つ」って大和言葉があるのに「待機」って漢語由来の単語があって、 待機の方がフォーマルな印象になるのと同じ。

a- (向かう)が付くことでニュアンスも変わるらしく、類語辞書を引くと、

  • wait → interval, downtime, halt, hold
  • await → anticipate, hope, ready for, look for

とかが並んでいます。要するに、

  • wait → 待機。止まって待つ(他の事せず待ってる印象あり)
  • await → 待望。気持ち的には待ってる(他のことをしてる印象あり)

という区別になります。

あと、「await は wait 単体じゃなくて、wait for と類語」とか言われたりします。 「A wait B」だと「A が B を待たせる」的な感じで、「A await B」「A wait for B」だと「AがBの到来を待つ」。

Q. これだから英語は

A. 「不遍・普遍」「市立・私立」「読んだ・呼んだ」で困ったことのない人だけが石を投げなさい。

「これだから自然言語は」という感じはするので、みんな形式言語で会話すればいいんじゃないでしょうか。

async + wait?

で、なんか、「await って、もしかして asynchronously wait の略で await なの?」とかいう都市伝説が発生しているらしく…

ちなみに、質問に関する回答は「it's serendipity」。

どうも、C# の仕様書に「await: asynchronously wait for the tasks completion」と書かれているからというのが理由みたいなんですけども。 言われてみれば、非ネイティブにはつらい文面。

英語の、前置詞によってニュアンスが変わるのは本当に難しい… とはいえ、接頭辞のカオスっぷりに比べると幾分かマシなので、 時代とともに「基本的な動詞 + 副詞/前置詞」で表現の幅を増やす用法が増えているという話もあります。

C# が await キーワードを採用

C# が非同期メソッドを導入する際には、文法をどうするか、特に await に相当する単語をどうするかはかなり悩んだみたいです。

Eric Lippert (当時の中の人)のブログ:

yield 案

元々、2010年のPDCでの最初の発表の時点では yield でした。

Task M()
{
    yield Task.Delay(1);
    var text = yield File.ReadAllTextAsync("a.txt");
}

yield と言えば、C# だとイテレーター構文があります。

IEnumerable<char> Producer()
{
    yield return '"';
    for (int i = 0; i < 3; i++)
    {
        yield return (char)(i + '0');
    }
    yield return '"';
}

void Consumer()
{
    foreach (var c in Producer())
    {
        Console.WriteLine(c);
    }
}

これで以下のような感じの挙動になります。

yield return

Producer 側で値を1つ作るたびに、その値を Consumer 側に渡すような動作。

yield は yield で日本人には何か難しい単語で、以下のような意味があります。

  • (車線などを)譲る・明け渡す・降伏する
  • 産み出す・収穫/利益が出る

語源的には「支払う」という意味らしく、後者の意味も「大地から恵みを支払ってもらう」「農作業の結果の支払いを受ける」みたいな感じでしょうか。

それか、win が「勝つ」の意味と「獲得する」の意味があるのと同じような感じですかね。 何かを得るために戦う。なので戦いに勝ったら何かもらえるのは当然、みたいな感じで。 「譲る」と言うからには「産出」を伴うのが当然というか。 略奪文化怖い…

で、「A yield B to C」と書くと、「A は B を(産み出して) C に譲る」になるみたいです。

イテレーターの yield return だと、「制御/スレッド/CPU の利用権を他のメソッドに譲る」みたいな感じで yield という単語が使われます。 でも、「A yield B to C」の用法があるので、 Producer yield return values of ", 0, 1, 2, " to Consumerで 「Producer が戻り値 ", 0, 1, 2, " を産み出して、Consumer に譲り渡す」みたいなニュアンスもたぶんあります。

さて、非同期メソッドの「yield 案」に話を戻しますが、 「制御/スレッド リソース/CPU の利用権を他のメソッドに譲る」という意味では、 非同期メソッドがやっていることはまさにこれです。 実のところ、イテレーターと非同期メソッドは、動作原理/C# コンパイラーのコード生成的な所は全く同じものです。 イテレーターの、非同期処理への応用結果が非同期メソッド。

しかしここで問題になるのが、「何かを産み出しつつ譲る」というニュアンスの方。 await Task.Delay(1)で、Producer は Consumer に Task を支払っているのかというと、そんなことはなく。 イテレーターの場合には譲り渡す相手が明確にいましたが、 非同期メソッドの場合は「スレッド リソースをOSに返還する」みたいな見えない部分での譲渡になるのがわかりにくいです。

それでも、イテレーターよりも先に非同期メソッドがあったのなら、もしかしたら yield 採用の芽もあったかもしれません。 とはいえ、似て非なるものに同じキーワードを使うのも怖いですし。 将来的には「非同期ストリーム」という、非同期メソッドとイテレーターを混ぜれる機能も入る予定なので、混ざる前提でキーワードを決める必要があります。

上記ブログには「yield return との区別が必要なら、yield with, yield for, yield while, yield until はどうか?」というコメントも結構多いくらいです。 ただ、ECMAScript 2017とかではむしろ、単に yield で generator (C# のイテレーター相当の機能)を表すことになっていますし、 yield return みたいな複合キーワード(2単語で初めてキーワード扱い)は総じて受けが悪いです。

他の案

他だと、Eric Lippert の挙げているのが

  • wait for
  • while away
  • hearken unto
  • for sooth Romeo wherefore art thou

コメントに挙がってるのが

  • after
  • continue with, continue after

とか。

wait for は、それとほぼ同義を1単語で表す await があるわけで。

while とか continue はループで使うやつと被るから駄目でしょうね。

after はどうだったんだろう。そもそも候補に挙げている人も少ないので、あんまりしっくりこないのかも。

hearken とか、wherefore art thou とかは完全にネタです。 await 自体が「古めかしい印象」の単語なので、 もっと古典な hearken unto (listen to) とか wherefore art thou (why are you: ロミオとジュリエットの「どうしてあなたは」)とかをシャレで挙げています。 まあ、「await は気持ち悪い?俺もそう思う。けど難しかったんだ」くらいの主張だと思われます。

await 案

ということで、最終的には await になりました。 前述の通り、await には「待望」(待ちつつも他の事やってる感じ)のニュアンスがちょっとあります。

async Task M()
{
    // wait = 待機
    // 止まって待つ
    // Thread.Sleep してるのと同じで、スレッド リソースを確保したままスレッドが止まる
    Task.Delay(1).Wait();

    // await = 待望
    // 気持ち待ってるけど作業は止めない
    // スレッド リソースを明け渡すので、OS はそのリソースを他に使える
    await Task.Delay(1);
}

とはいえ、await も割かしぎりぎり採用された感じです。 「なんか wait の古めかしい言い方」、「wait forと同じ意味」くらいにも思われる単語ですんで。 wait と await で内部的に起きる現象が違うというのはだいぶ危うい感じ。

とはいえ、メソッド M にとっては Task はやっぱり「完了を待ちたいもの」になります。 「待ちたいんだけど単純に待機しちゃダメ」と言うのが非同期メソッドの難しいところなので…

結局、去年リリースされた ECMAScript のやつも await になりましたし、 C++ に出てる提案が await なのは提案者が Microsoft なので当然として、 Python とか Swift で出ている提案も await を使う雰囲気になっていますし、 「わざわざ変えなきゃいけないほどは悪くない」程度には受け入れられている感じはします。

GetUnicodeCategory(int codePoint) を提案してみた

$
0
0

今日は、.NET で、U+10000 以上のコードが割り当たってる文字の Unicode カテゴリー判定をする方法について。 提案を出したらそのままプルリクを出すことになった話。

背景

U+10000 以上の文字

Unicode について詳細は、昔書いた記事があるのでそちらを参照。

ここではさらっと。

U+10000 以上に割り当たってる文字は、要するに、以下のようなものです。

  • Unicode 設計当初に想定していなくて、後から「追加面」(supplementary planes)として定義した
  • UTF-16 だと1文字で表せない
    • なので、サロゲート ペア(surrogate pair: 代理対)っていう2文字1組のコードで表す
  • UTF-8 だと4バイト文字になる
  • 一部のマイナーな漢字、マイナー言語の文字、絵文字なんかが入ってる

C# にとって問題になるのは、C# が作られた時期にはまだこの追加面文字が普及していなかったということです。 C# の文字列は内部的に UTF-16 だし、.NET の標準ライブラリは追加面の文字を扱うためのものがいまいちそろっていなかったりします。

まあ昔は本当に追加面にはマイナーな文字しかなかったのでそれほど問題にはならなかったわけです。 そう、絵文字がASCII圏でも広まり出すまでは…

Unicode カテゴリー判定

まあ、絵文字のおかげで、アメリカ産のソフトウェアでも「ASCII 文字しかまともに受け付けない」みたいな不具合は減ってきているそうですが。 その一方で、「追加面文字は全部『サロゲート ペア』扱い」、すなわち、絵文字と一部のマイナーな漢字もいっしょくたに扱われてしまったりする問題はいまだ結構あったりします。

例えば、実のところ、C# コンパイラーがまさにそうでして。 (昔、ちょっと勉強会で話したこともあるんですが)

C# は仕様上、Letter (書き言葉に使われる文字)なら何でも識別子として使えるはずなんですが、 今、追加面に入っている Letter は使えないという「仕様違反」があります。

// U+FFFF 以下 → 識別子に使える
var ᚠ = 1; // ルーン文字(U+16A0, OtherLetter)
var ʬ = 2; // 国際発音記号(U+02AC, LowercaseLetter)
var ℏ = 3; // 文字様記号、プランク定数(U+210F, LowercaseLetter)

// U+10000 以上 → 識別子に使えない
var 𩸽 = 4; // 一部のマイナーな漢字、ほっけ(U+29E3D, OtherLetter)
var 𓀀 = 5; // ヒエログリフ(U+13000, OtherLetter)

まさかのルーン文字以下の扱いを受けてる漢字があるとかʬʬ 草生えるʬʬʬʬʬ

これ、既知の問題でして、中国の方が報告はしているんですが。 そんなに「私もこれで困ってる」的な反響もなく、低優先度でずっと放置状態です。 (ちなみに、僕は C# コンパイラーの中身をいじってこの問題を修正したこともあるので、その気になれば修正プルリク出せます。需要がなさ過ぎてやる気も起きない…)

どうしてこういう挙動になるかと言うと、追加面文字のカテゴリーを正しく見ていないから。 C# は内部的に UTF-16 で文字列を扱っているので、 追加面文字の文字カテゴリーを見ようとすると、単にサロゲートという判定を受けます。

.NET のカテゴリー判定メソッド

.NET では、CharUnicodeInfoっていうクラスに Unicode 絡みのメソッドが定義されています。 (昔はchar型に定義されていて、互換性のために今も char にその手のメソッドはあるんですが。)

Unicode カテゴリー判定は以下の2つメソッドがあります。

使い方は以下のような感じ。

using static System.Console;
using static System.Globalization.CharUnicodeInfo;

class Program
{
    static void Main()
    {
        // char 用
        WriteLine(GetUnicodeCategory('ᚠ'));
        WriteLine(GetUnicodeCategory('ʬ'));
        WriteLine(GetUnicodeCategory('ℏ'));

        // 追加面(= char で表せないもの)用
        WriteLine(GetUnicodeCategory("𩸽", 0));
        WriteLine(GetUnicodeCategory("𓀀", 0));
    }
}

お分かりいただけるだろうか…

現状の .NET に追加面文字なんてものはないので、string で判定します。 サロゲート ペアになっている2文字の UTF-16 からカテゴリー判定するメソッドになっています。

内部が UTF-16 な文字列しか持っていないこれまだとそんなに困らなかったかもしれません。 でも、今、.NET にも Utf8String なんていうものが実装されようとしているわけでして、このままだと困ります。

というか、Utf8String の完成を待っていられなくて自作したり自作したりしてると、今でも困っています。

// 𩸽の UTF-8 バイト列
// ファイルとかネットとか、今時普通は UTF-8 で保存・送受信するでしょ
var utf8 = new byte[] { 240, 169, 184, 189 };

// UTF-8 をデコード
// 𩸽のコードポイント U+29E3D が得られてるはず
var c = Decode(utf8, 0);

WriteLine(c.ToString("X")); // 29E3D

// このコードポイントから直接カテゴリーを得る手段がない
var category = GetUnicodeCategory(c); // ここでコンパイル エラー
WriteLine(category);

(Decode メソッドの実体は Gist に。)

これ、もし現状の API でカテゴリー判定をしたければ、以下のような無駄なコードが必要になります。

// 一度 string に変換(= 無駄にヒープ アロケーションが発生)
var s = char.ConvertFromUtf32(c);
// string 版の GetUnicodeCategory を呼ぶ
var category = GetUnicodeCategory(s, 0);
WriteLine(category);

要するに、以下のようなメソッドをよこせと言いたい。

  • GetUnicodeCategory(int codePoint)

ちなみに、他のプログラミング言語はというと…

実は最初から実装ある

で、ですよ。 上記の、GetUnicodeCategory(String, Int32)中身を覗くじゃないですか。 だって、サロゲート ペアのままでテーブル引いてるわけないし。 絶対内部で一度コードポイントにデコードしてるだろうと。

そしたらやっぱり簡単に見つかるわけですよ、 InternalGetUnicodeCategory(int ch)とかいう internal なメソッドが。

public にしろと。

issue 立て、そしてプルリク

この話、いつから気付いていたかというと、昔C# コンパイラーの中身を書き換えたときなので、もう1年以上前だったりします。

まあ?標準ライブラリにUtf8Stringが入る頃までには? ほっといてもそのうちたぶん入るだろうし? 入る…よね? そもそもUtf8String自体がまだもうちょっと先っぽい? あれ…?

みたいな。

issue 立てました

ということで、観念して突っついてみることに。 issue 立てました。

roslyn とか csharplang とかなら普段から割かし張り付いてるので勝手もわかるんですが。 corefxは勝手がわからず結構悩んだり。

そもそも、こういうプリミティブ型絡みの機能は corefx (標準ライブラリ)の方じゃなくて、coreclr (実行環境)の方に含まれていますし。 それとは別に、最近は「設計に関する提案はこちら」みたいな dotnet/designsとかいうリポジトリもありますし。 どれに、どう書けばいいの…

結局、corefx に出したわけですけども、まあ、一応それでも受け付けてもらえたみたい。

ただ、corefx に対する提案のフォーマットとしてはダメだったみたいで、

  • このドキュメントに沿って整形して
  • あっ、こっちで提案文章を更新しといたわ、これでいいか?

みたいなコメントが寝ている間に書かれていてビビったり。 (あちらはアメリカ西海岸なので、日本時間で言うと深夜2時くらいから稼働開始。こっちが寝てる間に話が進む。)

レビューされました

提案 issue が corefx チームの目に留まった場合、 当然ですがレビューが掛かります。 今は、レビューの様子も毎回 YouTube に投稿されていたりします。 (Immo (.NET 標準ライブラリ周りのプログラム マネージャー)が投稿。)

何気に今週のレビューの様子を見てみたら、割かし最初の方にその提案 issue が取り上げられてるじゃないですか。

(1分30秒頃から)

英語半分も聞き取れないんで大体ですけども…

  • やっぱ ASCII 圏の人は追加面文字とか知らない
    • string 版のやつは何か特殊なことしてるのか?char ではダメなのか?みたいな
    • 詳しそうな人がチームメイトに向かって「サロゲート ペアとは」みたいな説明を開始
  • public にしてリネームするだけだしいいだろう、ただし、引数名はちゃんとしないと
    • 提案では Unicode scalar とか code point とか書かれてるけど、どれがいいんだ
    • scalar とか code point とかの説明も開始
    • じゃあ、code point の方だな

みたいな感じで、10分程度でレビュー完了。 結果がこちらのコメント: Looks good but we should rename the parameter

ちなみに補足。scalar と code point は以下の差らしい。

  • scalar: サロゲートになっている文字コード(前半 U+D800 〜 U+DBFF、後半 U+DC00 〜 U+DFFF)部分は除く
  • code point: サロゲートの範囲も含む

GetUnicodeCategoryの場合は、サロゲート文字を食わせるとSurrogateを返してくるので、この範囲が除外されている scalar ではなく、含んでいる code point が正解とのこと。

プルリクを出す権利をやろう

レビューが通ったわけですけども、そしたらこんなこと言われたわけですよ。

この API の実装に関して、ヘルプはほしい?そうする意思があるならガイドをできるけども。

えっ、あっ、はい。 こちらでプルリク作るんですか。

この作業、 どう考えても「中の人」がやるのが手っ取り早いわけですよ。 public にしてリネームするだけ。 それも、Visual Studio でリファクタリング機能を使えばほんとに一瞬で、 普段から関わっている人が作業すれば1分で済むやつ。

それに対して、英語もカタコト、時差もある相手にプルリクをオファー。 どう考えても、「提案者であることに免じてコミット履歴に名前を残す権利をやろう」的な接待モード。

というか、実際接待。 いただいたガイドも、「この行をリネームしたプルリクを出してくれ」。 作業指示がきわめて具体的な上、テストとか要らないんですか。 テストはそちらでやっていただけるんですか。 はい、プルリク出します。

条件コンパイルの罠

もう1度言いますが、「Visual Studio でリファクタリング機能を使えばほんとに一瞬」。

しかし、そこには罠があったのです。

クロスプラットフォームなものに関わらないとあんまりはまることがない罠なんですが。 クロスプラットフォームなものを作ってると、結構、条件コンパイルだらけになります。 Windows 向けビルドでしか通らない場所、Unix でしか通らない場所…

はい、やらかしました。Unix ビルドを壊すやつ。 IDE のリファクタリングって、条件コンパイルの全条件までは追えないんですよねぇ。

世間一般にも、条件コンパイルはビルドを壊す原因ナンバー1。 だって、手元ではビルド通ってるんだもん。 そのままコミットしちゃうじゃない?

しかも今回に関しては、C# の#if プリプロセス分岐ですらなく。 csproj 中に、「ItemGroup ConditionTargetsUnix っていうプロパティが定義されている場合だけこのソースコードを使う」みたいな記述が書かれてるやつでした。 自分が立ち上げてた Visual Studio からはファイルが見えてすらいない…

そしてこれもまた、僕が寝ている間に「直しといたよ」コミットが追加されているという接待っぷり…

その後

coreclr に出したプルリクはマージされたみたいですね。

ただ、その後も関連作業が続いているみたいです。 単に coreclr にある mscorlib の実装だけじゃなくて、それを corefx 側に公開したりする作業が必要だそうで。

そっちでも、寝ている間に「テスト通らないなぁ」→「あっ、このテスト失敗は別件だわ、大丈夫」みたいな会話がなされており。

まとめ

  • ASCII 圏の人、ほんとに Unicode 追加面を知らない
    • 「こんなのほっといてもすぐに追加されるだろう」とか甘い
    • たぶん、日本人か中国人が言い出さないと進まない
  • プルリクを出す権利をやろう
    • 接待モード
    • 時差的に、寝てる間にいろいろ起こってる
    • 条件コンパイルの罠

Pickup Roslyn 1/21

$
0
0

今日は coreclr, corefxlab, designs から1件ずつ、計3つ。

C# スクリプトの実用

なんかドキュメント生成系のスクリプトを1個、sh から C# スクリプトに置き換えてみるのを試したいらしい。 曰く、

  • 典型的なスクリプト作業がどの程度効率化するか知りたい
  • あんまり重要でないものでとりあえずドッグフーディングを始めたい
  • m4への依存を減らしたい
  • C#ベースのスクリプト利用の強み・弱みの知見を得たい

とのこと。

あと、今の (.NET Core 向けのは) Regex が遅すぎてやってらんないからこのプルリクでは Regex クラスの利用を避けてるらしい。

priority queue

今更ながら、priority queue の実装するみたい。 とりあえず、corefxlab でお試し実装を提案中。

割かし「なんで .NET にはないんだろう?」と言われ続けてるデータ構造筆頭。

なんかさらっと見てる感じ、priority queue に mutable なデータを入れたあと、優先度が変わるような変化を書けたときがまずそうな感じ。

.NET Core Runtime と .NET Core SDKのバージョン

.NET Coreって、今、Runtime (.NET 製プログラムを動かすための実行環境)とSDK (コンパイラーとかを含む)のバージョン番号がずれてて本当にわかりにくく。

前々からそれに文句を言ってた人が、「上2つの数字(メジャーバージョンとマイナーバージョン)くらいは揃えよう」っていう提案文書を提出。 まあ、全くもってその通りで。

ちなみに、まあ、SDK には「C# コンパイラーのバグ修正のみのリリース」みたいなのがあるので、Runtime と SDK のバージョンを完全に足並みそろえるってのはできないそうです。 なので、上2つのみの統合。

あと、SDK の方の3つ目のは、「基本、100単位でバージョンを上げる。バグ・セキュリティ ホール修正のサービス リリースは1ずつ上げる」みたいなのを提案。

小ネタ null関係の演算子

$
0
0

今日は、?.とか??での、nullの判定方法について。

C# 6で導入されたnull条件演算子(?.)ですが、以下の2つの式がほぼ同じ意味になります。

x != null ? x.M() : null
x ?.M()

「ほぼ」であって「完全に同じ」と言えないのは、==演算子を呼ぶか呼ばないかが変わってしまうせいです。 前者(自分で==を呼んでいるやつ)はオーバーロードされた==を呼び出しますが、 後者(?.を利用)は呼びません(直接nullかどうか調べます)。

例えば、以下のように、本当はnullじゃないのにnullを自称する(x == nullがtrueになる)クラスを作ると、ちょっと変な挙動になります。

using static System.Console;

class NonDefault<T>
{
    public T Value { get; }
    public NonDefault(T value) { Value = value; }

    public override string ToString() => Value.ToString();

    // Value が既定値のときに null と同値扱いする
    // null でないものとの x == null が true になることがある
    public static bool operator ==(NonDefault<T> x, NonDefault<T> y) =>
        ReferenceEquals(x, null) ? ReferenceEquals(y, null) || Equals(y.Value, default(T)) :
        ReferenceEquals(y, null) ? ReferenceEquals(x, null) || Equals(x.Value, default(T)) :
        Equals(x.Value, y.Value);

    public static bool operator !=(NonDefault<T> x, NonDefault<T> y) => !(x == y);
}

class Program
{
    // null の時には "null" と表示する ToString
    static string A(NonDefault<int> x) => (x != null ? x.ToString() : null) ?? "null";
    // A とほぼ同じ意味に見えて…
    static string B(NonDefault<int> x) => x?.ToString() ?? "null";

    static void Main()
    {
        WriteLine(A(new NonDefault<int>(1))); // 1
        WriteLine(B(new NonDefault<int>(1))); // 1

        WriteLine(A(null));                   // null
        WriteLine(B(null));                   // null

        // == を呼ぶ呼ばないことによる差がここで出る
        WriteLine(A(new NonDefault<int>(0))); // null
        WriteLine(B(new NonDefault<int>(0))); // 0
    }
}

まあ、普通、こんな==演算子オーバーロードの仕方はしないんですが。 というか、参照型に対する==オーバーロード自体めったにしないんですが。 (通常、==演算子を使うのは、Dictionaryのキーにしたい不変なクラスくらいです。)

ちなみに、このメソッドABのコンパイル結果はそれぞれ以下のようになります。 比較のために表にして命令ごとに並べてみましょう。

A B
ldarg.0 ldarg.0
ldnull brtrue.s IL_0006
call NonDefault::op_Inequality  
brtrue.s IL_000c  
ldnull ldnull
br.s IL_0012 br.s IL_000c
ldarg.0 ldarg.0
callvirt Object::ToString callvirt Object::ToString
dup dup
brtrue.s IL_001b brtrue.s IL_0015
pop pop
ldstr "null" ldstr "null"
ret ret

nullの判定方法(2行目~4行目)だけが違って、残りは全く同じです。 ==演算子を呼ばずに直接nullを調べるならbrtrue命令1個でできます。

ちなみに、brtrueは"branch if true"の略で、 「直前の結果がtrueだったらジャンプする」という命令になります。 整数の0とか、参照型のnullとかはfalse扱い。

この挙動はnull合体演算子(??)でも同様です。

おまけ: throw null

話題は変わりますが、?.の中身をILレベルで覗いたついでと言ってはなんですが、ちょっとしたおまけ。

時々、「throw nullと書くと、throw new NullReferenceException()と同じ意味になる」的な誤解(?)を見かけたりします。 コンパイル結果的には当然、全然違うんですよね。

以下のように書いた場合、

static void X() { throw null; }
static void Y() { throw new NullReferenceException(); }

コンパイル結果は以下の通り。

.method private hidebysig static void  X() cil managed
{
  // コード サイズ       2 (0x2)
  .maxstack  8
  IL_0000:  ldnull
  IL_0001:  throw
} // end of method Program::X

.method private hidebysig static void  Y() cil managed
{
  // コード サイズ       6 (0x6)
  .maxstack  8
  IL_0000:  newobj     instance void [mscorlib]System.NullReferenceException::.ctor()
  IL_0005:  throw
} // end of method Program::Y

割かしそのまんまなILコードです。 nullをロード(ldnull)して、throw命令を実行。 要するに、前者は(throw命令の実行に失敗して)実行エンジンがNullReferenceExceptionを作って投げていて、 後者は自分自身で作ったNullReferenceExceptionを投げている。 まあ、結果的には同じような挙動をするんですが。


++C++; // 未確認飛行 C をリニューアルしました

$
0
0

「++C++; // 未確認飛行 C ufcpp.net」 をリニューアルしました。これに伴い、ブログも統合し「WordPress https://ufcpp.wordpress.com/」から「http://ufcpp.net/blog」へ変更となります。

2000年よりサイトを運営しているため、一部古い内容のものがあります。ご了承ください。

誤字・脱字・バグを見つけた場合や、ご意見・ご要望等がございましたら、GitHub の Issues までご連絡いただけますと助かります。

前のバージョンでは XML を XSLT でパースし、静的に HTML をホストしていました。リニューアル後では、Umbraco 7 を使用しています。Umbraco は、.NET Framework, ASP.NET で動作するオープンソースの CMS です。 ASP.NET 公式サイトも Umbraco で構築されています。

Build 2015

$
0
0

Build 2015 の資料に目を通すの数日かかった… 結構長し読みだったんですけども。

CEO交代後のマイクロソフトの

  • デバイスとサービスの会社になる
  • すべての開発者、すべてのデバイス、すべてのプラットフォームにアプローチ

という方針がついに形になって表れ始めたという印象でした。

デバイス

発表の3分の1くらいは「Windows 10」がらみでした。

ようやく、デスクトップとモバイルの分断が解消されそうというか、WindowsとWindows Phoneが統合されそうというか。

Universal

これまで 「ストア アプリ」とか変な名前だったけども、「Universal Windows Platform」「Universal Windows Apps」と、やっとまともな名前になった印象。

完全に単一のバイナリ

IoT、Phone、Tablet、デスクトップ、大スクリーン(XBoxゲーム、Surface Hubなどの会議システム)すべてをカバー。 (Windows 8.1の「Universal Apps」はWindowsとWindows Phoneは別バイナリ、ストアが単一化されてたのと、Shared Projectでのコード共有ができるようになってただけ)

そのために、

  • レスポンシブ デザイン
    • スクリーン サイズに応じて Visual State Managerとか使ってXAMLのデザインを分岐
    • (XAML的にも、XAML中のタグを遅延ロードするような機能(x:DeferLoadStrategy)が増えてたり)
  • API Contract
    • メソッド名や型名指定でAPIがデバイス上で使えるかどうかを調べて分岐する処理を書く

とかやるように。

サービス

同じく発表の3分の1くらいは「Azure」がらみでした。といっても、OSとかハードウェア(IaaS的なクラウド)じゃなくて、「サービス」が主役。

などなど、細粒度のサービスをマイクロソフト自身が売ったり、サービスの開発・登録・検索・消費などをしやすくするための基板を提供したり。 マイクロソフトが自社の強みを活かしてクラウド戦略を推し進めてますね。

すべての開発者に

.NET/C#を使える理由もだいぶ増えるし、使わなくていい理由もだいぶ増える印象。 いろんな言語・環境を使っている人が、フェアに相互に行き来できるよい環境ができそう。

すべてのプラットフォームで.NET開発

  • Visual Studio Code
  • .NET Core
    • Windows x64環境向けはRCに Linux, Macはベータ
    • Xamarinは今、モバイル(Xamarin.iOS, Xamarin.Android)に注力していて、サーバー側機能はプロダクション環境向けのサポートしていない

Visual Studio Codeとか、中身は単にElectron + Monaco、つまり、これまでもVisual Studio Online向けに作っていた、Webなエディターを、Electron使ってアプリ化しただけ。 実装に関する手間は対してかかっていなさそうだし、名前に反して「Visual Studio」とは完全に別系統のアプリなんですが。 とはいえ、「Visual Studio」の名前を関したアプリとして.NET関連のどこかのチーム(たぶんASP.NET系な雰囲気がする)が提供・保守していくということの。

Visual Studio利用者にすべてのプラットフォーム開発機能を提供

コンパイラーが提供されているって意味では、C++は一番クロスプラットフォームなプログラミング言語のはずなんですけども。 とはいえ、プラットフォーム固有のAPI・ライブラリ参照であるとか、プロジェクト構築とか考えるとそんなに容易くない。 それが今回、クロスプラットフォーム向けのC++テンプレートが提供されて、Visual Studio上でAndroidやiOSアプリをC++でようになりました。

Office 2016がUniversal Apps化して、AndroidやiOS版もリリースされて、しかも、コードの9割以上を共有しているらしく、そのおこぼれな感じはします。 Officeの影響はやっぱり大きいですねぇ。Officeの性能を出すためにXAMLにも結構新機能が入ったそうですし。

すべてのプラットフォーム開発者にUniversal Windows Appsの開発機能を提供

Bridges(相互の橋渡し)の各種プロジェクトは、コードネームがAndroidのA、iOSのI、ClassicのC、WebのWを頭文字とするワシントン州の地名なのかな、また。 レジストリの仮想化とか、Android/iOSのAPIの互換レイヤーの提供というものみたい。

これまで、スマホやタブレット対応のために一度AndroidやiOSに移っていったエンタープライズ企業をWindowsに呼び戻すよい戦略。 一方、コンシューマーだとどうだろう…という感じはします。

全APIの完全互換とかはたぶん無理だろうし、実際、Buildの資料にも「APIのサブセットを提供」って書かれてて。 「AndroidやiOSアプリのコードをほぼそのまま持ってきて、少し書き替えるだけでUniversal Windows Apps化できる」とか言っていますが、どこまで楽に移植できるのかはちょっと心配。 試してみたいものの、ダウンロードするのに登録が必要だったり、手持ちにそれなりの規模のAndroid/iOSアプリがないので。

ピックアップ Roslyn 5/14

$
0
0

Build 2015の資料に目を通すので忙しくてサボっていましたが。

まあ、もっとも、C#チーム、Visual Studioチーム的にもRC版が出たばかり、かつ、RTMも近いだろう状況で、GitHub上の動きを見ててもバグ修正で多忙な雰囲気を感じます。

そんな中、いくつかピックアップ。

Unicode version number

https://github.com/dotnet/roslyn/issues/2648

「C#では、常に実行環境の最新のUnicodeバージョンを利用したいので、仕様書上はUnicodeバージョン番号を消そう」とのこと。

基本的にはUnicodeって追加される一方なので、そのほうがいいですよね。レアケースもあるにはあったんですが。

csc2/vbc2 撤廃

https://github.com/dotnet/roslyn/pull/2711

Roslyn製の新コンパイラーは、2種類(C#, VBともに2種類ずつ)ありました。

  • csc/vbc.exe: 以前の(Visual Studio 2013までの)コンパイラーと同じような動作のスタンドアローン動作版。
  • csc2/vbc2.exe: 複数のプロジェクト間で標準ライブラリなどの読込結果を共有するために、Windowsサービスとして常時動いてるコンパイラーに仕事を投げるもの。

今は、csc/vbc 側が内部的に KeepAlive オプションとかを持っていて、旧 csc2/vbc2 的な動作もできるみたいです。

今の、Visual Studio 2015 RC版で、「"csc2.exe" はコード 1 を伴って終了しました。」とかいう謎エラー メッセージが出てくる問題、そろそろおさらばできそうかも。

2進数リテラル、_ 区切りの実装

https://github.com/dotnet/roslyn/pull/2730

そういえば、C# 6.0から漏れてたのよね、忘れてたけども。0b101001 みたいな2進数リテラルを書ける機能と、0xff_ff_ff_ffみたいな、_ で数値リテラルを区切れる機能。

割りかし実装は簡単な類の機能なわけですが、それでもこんな、C# 6.0 RTMの直前(冒頭で書いたとおり、今はRTMに向けてバグ修正で手一杯のはず)になんで?

と思って調べてみたら、このpull-req主、マイクロソフトにインターンで入って来た学生っぽい。確かに手頃な課題。

メソッド名に ! 記号を使わせて

https://github.com/dotnet/roslyn/issues/2696

要するに、Rubyな人がRubyの命名規約習いたいから、Enlarge! (記号の!までがメソッド名)みたいな識別子を認めてほしいと要求。

まあもちろん、そんな要求そのままは受け入れがたいわけですが。とはいえ、いくつかわからなくもない話の流れが。

  • Rubyで末尾に!をつけるのは、「引数で受け取ったオブジェクトに対して副作用をかけますよ」というのを示す規約
    • 例えば X M(X x)はメソッド内で新しいXのインスタンスを作って返すのに対して、void X!(X x)はインスタンスxを書き換える
    • あくまで命名規約。守られる保証はない
  • C#的にはちょっと
    • 守られる保証のない命名規約自体、C#は望まない
    • 語尾にPureであるとか、多少煩雑になるけども、最初から使える文字を使うべきではないか
    • [Pure]属性をつけるとか、属性ベースでやるのがC#っぽいのではないか
  • ただ、属性ベースだと…
    • 利用側、ぱっと見で区別が付かない
    • 確かに副作用を起こすかどうかはぱっと見で区別ついた方が良さそう
    • 特定の属性が付いている場合に、IDE上での表示(色など)が変わる機能でもあればいいんだけど
  • IDE上の表示で区別するとして
    • それって、Visual Studioはともかく、今どきだったらXamarin Studio, Visual Studio Code, OmniSharpなどなど、いろいろ選択肢はあるけども、その全てが対応してくれなきゃ嫌だ

的な。

まあ、副作用起きるというのが目に見えてほしいという点に関してはわかる。

Visual Studio 2015 RC と pdb2mdb

$
0
0

Unity (というか、Visual Studio Tools for Unity)とVisual Studio 2015 RCとの間での問題で、PDBからMDBへの変換がうまく動いてなくて困っている(一時的な対処方はなんとかできてる)という話。

PDB, MDB

まず、PDBって何かの簡単な紹介から。

PDBはProgram Databaseの略です。

Program Databaseは、実行ファイルのどの辺りが、ソースコードの何行目でどのメソッド通ってるかとかの情報が入ったファイルです。Visual Studio上でブレイク ポイント仕掛けて止めたり、スタック トレース追えたりするのはこの情報ありき。

とはいえ、この手のProgram Databaseには規格とか標準がなくて、Visual Studioが使ってる形式がPDB(拡張子も .pdb)で、Monoが使ってる形式がMDB。別形式です。

一応、Monoはpdb2mdb っていうツールを提供していて、PDBからMDB形式に変換できたりはします。

Xamarin.Android, Xamarin.iOS, Unityとかに対してVisual Studioを使った開発がしたい場合、PDBからMDB変換して実行することになります。だいたいは裏で勝手にやってくれるんですが、それが最近うまく動いていない状態だったりします。

PDB の形式変わったみたい

何のせいで問題が出ているかというと、PDBの形式が変わったみたい。

Unityについてくるバージョンのpdb2mdb(Unityインストール フォルダー以下、 Editor\Data\MonoBleedingEdge\lib\mono\4.0\pdb2mdb.exe にある)とか、NuGet Gallaryから取れるバージョンだと、

Unknown custom metadata item kind: 6

とかいうエラーを吐いて停止。

monoのGitHubリポジトリを見てみたところ、先月に

っていうコミットがあったんで、たぶんこれですかね。PDB中のdecimalの値がちゃんと読めていなかったらしい。

ちなみに、Visual Studio 2015 RC付属(インストーラーでチェックを入れれば一緒にインストールされる)Xamarin.Androidだとこのコミットとりこみ済みみたいで、正しく実行できます。

最新のpdb2mdb

が、Visual Studio 2015 Preview Tools for Unity がまだこれに対応していないくさくて、Visual Studio 2015でビルドしたDLLをUnityに持ってきても、MDBファイルができなくなっておりまして。

(最終更新日時4/30ってなってるのをインストールしてみてもダメでした。インストールしなおしがうまく行ってないのかもしれないものの。)

あと、この問題が解消したバージョンのpdb2mdbのありかがわからず…

結局、Xamarin.Androidでは動いているはず(= 問題解消済みのpdb2mdbが動いているはず)なので、

  • Visual Studio 2015同梱のXamarin.Androidのプロジェクト テンプレートを覗いて
  • \Program Files (x86)\MSBuild\Xamarin\Android に関連ファイルがあって
  • Xamarin.Android.Build.Tasks.dll ってDLLの中にpdb2mdb相当の処理が入ってることを調べて
  • Pdb2Mdb.Converter.Convert静的メソッドを呼べばいいこと突き止めて
  • PowerShell スクリプト書いて実行

とかいう作業を今日やったり。

Visual Studio Tools for Unityがちゃんと対応するまでの一時しのぎではあるんですけども。嫌な処理を書いてしまった…

ピックアップRoslyn 5/27

$
0
0

引き続き、Roslynリポジトリは「RTMに向けてバグ修正で手いっぱい」感漂って来てて、新しい話はあまりないんですが。

というか、割かし重複提案なissueが立って、速攻で「それ、これとの重複じゃない?」→「重複だった…」的な流れになってるものが多々。まあ、issueが800件超えっぱなしですからねぇ、このリポジトリ。

そんな中いくつか。

Support the tadpole operators #3072

https://github.com/dotnet/roslyn/issues/3072

完全にネタというか、ネタに振り回されてるというか。

大元の火種はこれ: New C++ experimental feature: The tadpole operators

The Old New Thing」は、「Windows秘話」「Windows温故知新」的に、「Windowsのここがダメだ」とか言われまくってる辺りに対して、何でそんな風になってるのかとか、そんな話を面白おかしくネタにしているブログ。有名な奴だと、「『Windowsはいつも無駄に見た目ばっかり変えやがって』って文句言うけどさ、中身だけ無茶苦茶進化してて、見た目が変わってない電卓は、ずっと進化してること気づいてもらえなかったんだぞ」みたいな話(「When you change the insides, nobody notices」)とか。

で、今回やらかしたネタが、「オタマジャクシ演算子」。機能的に言うと、「副作用のない++, --」、「高優先度の単項+1, -1演算子」。

構文 意味 覚え方
`-~y` `y + 1` オタマジャクシが値の方に向かって泳いで、値を増やす。
`~-y` `y - 1` オタマジャクシが値から去るように泳いで、値を減らす。

これを、「Visual Studio 2015 RCで実装してみた実験的な試み」とか言って紹介(もちろん嘘)。The Old New Thingの方のコメントでも、Roslyn issueページの方のコメントでも、割かしみんな盛大に釣られてるっぽい。

The Old New Thingの次のブログ エントリーでネタを明かしていますが、これ、別に「実験的に実装したもの」じゃなくて、現状の正規のC++文法で(C#ででも)認められた普通の演算です。2の補数とかを知ってれば簡単にわかるんですが、ビット反転 ~ と符号反転 - は1違いの数字になるので。単にこれを並べただけ。

なんというか。4月1日にやれよ…

Provide simple syntax to create a weak-referenced eventhandler #101

https://github.com/dotnet/roslyn/issues/101

弱イベント作れる専用構文ほしいという要望。

まあ、気持ちはわからなくはないけども。うちのサイトにも「弱イベント」の話ありますが。

乱用すると性能面での影響が無視できない、かつ、何も知らないと簡単に乱用される機能なんですよねぇ。個人的にはあると楽になるけど、ほんとにあったらやばそうな機能すぎて、ちょっと。

Compilation without generics #3064

https://github.com/dotnet/roslyn/issues/3064

.NET Micro Frameworkではジェネリック使えないんだし、C#コンパイラー的に「ジェネリックを認めない」版を提供してくれないかという要望。

というか、C# 4.0以降、自動実装イベントから生成されるコードが変わって、中身でジェネリック版Interlocked.CompareExchangeを使うようになったせいで、.NET MFでイベントが使えないという問題と合わさって結構深刻みたい。

.NET MF自体が今後どうなの?という感じもあり。

まあ、この場合、本当の問題は、自動実装イベントがInterlocked.CompareExchangeに依存してることな感じも。コンパイラー生成コードが、undocumentedに、何か特定のAPIに依存しているってのはあんまりよくない状態。

Viewing all 483 articles
Browse latest View live