dotnet/csharplangより
2019/07/30に、C#の言語仕様検討用リポジトリに新しい議事録が追加されました。
普段は軽く流して終わりなのですが、面白そうな新機能の提案があったのでまとめて見ました。
使う側(つまり我々)に関係ありそうなのところだけを抜き出しているので、背景等を知りたい方はリンク先をご確認ください。
あと、I am English noobなので、なにかおかしいところがあったら指摘してください。
注意
まだ提案レベルなので、本当に実装されるのかは未定です。
initonly
オブジェクト初期化子による初期化
例として、下記のクラスを用います。
public class UserInfo
{
public string Username { get; set; }
public string Email { get; set; }
public bool IsAdmin { get; set; } = false;
}
一般的に、こんな感じで初期化すると思います。(オブジェクト初期化子)
void M()
{
var userInfo = new UserInfo()
{
Username = "andy",
Email = "angocke@microsoft.com",
IsAdmin = true
};
}
コンストラクタによる初期化と比べて、大きな利点が3つ。
- 変更に強く、プロパティの移動や追加が簡単。
- 任意の順序で設定できて、初期化時の名前がプロパティ名と一致する。
- デフォルト値を持つプロパティは単純にスキップできる。
そして、最大の欠点が1つ。
- プロパティが変更可能である。
この問題を解決するために
プロパティとフィールドに適用できる新しい修飾子、initonly
を提案します。
public class UserInfo
{
public initonly string Username { get; }
public initonly string Email { get; }
public initonly bool IsAdmin { get; } = false;
}
変更はとても簡単で、内部的にはreadonly
にするだけらしいです。(サンプルコードが乗ってたけどよく分からんかった)
CLR的には検証不可能だけど、危険ではないそうです。が、より高度な検証をサポートするために新しい規則が提案されていました。(読んだけどよく分からんかった)
この方法を用いれば、多くの問題を解決できるし、複雑な変更も要しません。
しかし新しい問題もあり、変更を加えたオブジェクトの生成が難しい事です。
with
変更を加えたオブジェクトの生成が難しい
不変性を意識してプログラミングする場合、オブジェクトに変更を加えることは、オブジェクトに直接変更を加えるのではなく、変更を含むコピーを作成することによって行われます。
(翻訳結果をベタ貼りしたんで分かりにくいですが、こう言うことがしたいってことらしいです。)
// このオブジェクトの名前を変えたい場合、
var userInfo = new UserInfo()
{
Username = "andy",
Email = "angocke@microsoft.com",
IsAdmin = true
};
// userInfoをコピーして、Usernameだけ変えた物を作る(元とは別のオブジェクト)
var newUserName = ...
しかし、現在のC#では簡単にこれを行う便利な方法はありません。
この問題を解決するために
これを実現するために、オブジェクト初期化子に似たwith
式を追加することを提案します。
var userInfo = new UserInfo()
{
Username = "andy",
Email = "angocke@microsoft.com",
IsAdmin = true
};
var newUserName = userInfo with { Username = "angocke" };
生成されたnewUserName
はuserInfo
に"angocke"がセットされたコピーになります。
ここでの違いは、作成中の新しいオブジェクトが単純な新しいオブジェクトの作成ではなく、元のオブジェクトの複製であることです。
この機能を実現するには、オブジェクトが複製オブジェクトを生成する「with
コンストラクター」を提供する必要があります。 サンプルは次のようになります。
class UserInfo
{
...
[WithConstructor] // placeholder syntax, up for debate
public UserInfo With()
{
return new UserInfo() {
Username = this.Username,
Email = this.Email,
IsAdmin = this.IsAdmin
};
}
}
with
コンストラクタが検証を必要とする場合、ユーザはその検証を行うためにコンストラクタを導入することができます。
class UserInfo
{
...
private UserInfo(UserInfo original)
{
// validation code
}
[WithConstructor]
public UserInfo With() => new UserInfo(this);
}
with
に関連した最後の複雑さは継承です。 レコードが拡張可能な場合は、サブクラスに新しいwith
を提供する必要があります。 これは次のようにして実現できます。
class Base
{
...
protected Base(Base original)
{
// validation
}
[WithConstructor]
public virtual Base With() => new Base(this);
}
class Derived : Base
{
...
protected Derived(Derived original)
: base(original)
{
// validation
}
[WithConstructor]
public override Derived With() => new Derived(this);
}
さらに、with
コンストラクタを派生型でオーバーライドする場合、別の問題があります。(が、別の提案があるのでそちらで議論されているらしい。)
data
そんな面倒なコード、毎回書いてられない
上記の機能は、以前は非常に困難だったプログラミングスタイルを簡単に実現できます。 しかし、新機能があっても、すべてを自分でアノテーションするのはかなり冗長でエラーが発生しやすい可能性があります。
Equals
やGetHashCode
のような項目を自ら書くこともできますが、面倒なのと同じです。
この問題を解決するために
デフォルトで設計されたコードを自動生成するための構文を提案します。 構文例は次のようになります。
data class UserInfo
{
public string Username { get; }
public string Email { get; }
public bool IsAdmin { get; } = false;
}
最後に
議事録を見た感じ、(懸念事項は多いものの)前向きに検討されているオーラが出てる気がします。
流石にC# 8.0には間に合わないと思いますが、実装されると個人的には非常に嬉しいですね。
(今組んでいるプログラムが、まさにこの問題をかかえているので。)
ちなみに、C# 8.0は2019/09に正式リリースされる予定です。(.Net Core 3.0と同時)