15
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

C#にあったらいいなと思う記法・言語仕様

Last updated at Posted at 2025-11-28

C#も 14 になり、とても書きやすい良い言語に進化していますが、あったらうれしいな、と思う機能、言語仕様はまだあります。
(注意:個人の感想です)

dotnet/csharplang で提案や議論があるものはそのリンクも貼っておきます。

1.readonly ローカル変数

Proposal: 'readonly' for Locals and Parameters

現在の言語仕様では以下はコンパイルエラーです。
const キーワードはコンパイル時定数である必要があります。

int a=10;
int b=20;
const int c = a + b; //コンパイルエラー
const int d = Random.Shared.Next(); //これもコンパイルエラー

モダンな言語にはローカル変数を実行時定数(再代入不可)として扱う方法があり、C#でも同じ設計思想でプログラミングしたいと思う人は多いと思います。

多くの提案、議論 (#8479, #115, #188など) がされてましたが、残念ながら実装予定はありません(Likely Never)。C#デザインチームからのリジェクトの理由は書かれていますが、とても残念です。

2.readonly パラメータ

こちらはメソッドの引数の読取専用化です。

public void Test(int a) {
    a=10; //引数aを変更できちゃう
}

C#はメソッドの引数が変更できますが、これをreadonlyにしたいという話です。
読取専用ローカル変数と一緒に提案されていましたが、切り離されて検討の遡上に残ってます。
これはC#12でプライマリコンストラクタが導入されたことにより、コンストラクタ引数を(クラス内のメソッドで)変更できてしまう事も大きな要因のようです。

//プライマリコンストラクタを使ったクラス
public class A(int a)
{
	public void Method1()
	{
		a = 1; //コンストラクタの引数が参照、変更できちゃう
	}

    //現状の対策はreadonlyフィールドに最初にコピーしておく
    //DI利用者には違和感ないが・・
    private readonly int _a = a;
}

csharplang/proposals/readonly-parameters.md

(適当な翻訳)
C#ユーザーは長年、ローカル変数と引数の両方にreadonly指定を可能にする機能を求めてきました。C#デザインチームは主に2つの理由からこれを控えてきました:

・引数やローカル変数へのreadonly指定は、むしろ迷惑な追記になる。
・ローカル変数向けの簡潔な構文について結論が出ていないこと。

しかし、プライマリコンストラクタの導入により、少なくとも引数に関してはこの判断が変わりました。ユーザーはプライマリコンストラクタ引数が変更されないことを保証したいと考えています。(プライマリコンストラクタの)引数のスコープははるかに広いため、意図しない変更の危険性が格段に高くなります。したがって、引数修飾子としてのreadonlyを許可することを提案します。

3. try-catch句でのyield return

Allow yield inside of try / catch and catch
いまだに無意識に書いてしまうことがありますがコンパイルエラーです。

IEnumerable<int> M1(IEnumerable<z> col)
{
    try
    {
        foreach (var item in col)
        {
            var num = Convert(item);
            yield return num;        //ここにyieldはだめ
        }
    }
    catch (Exception ex)
    {
        throw;
    }
}

進展してほしいところですが、「必要性はあるが、C#開発チーム自身で実装する可能性は低く、コミュニティの貢献を待っている」状態のようです。
Champion: Remove error "Cannot yield a value in the body of a try block with a catch clause" #8994

4. dictionary式

[Proposal]: Dictionary expressions

配列やListはc#12で int[] i = [1,2,3]; のように コレクション式 で書けるようになりましたが、これのDictionary版です。とはいってもすでに以下の書き方はあるのでこれに近くなるのではと予想。

var dic1 = new Dictionary<string, int>() {
	{ "one", 1 },
	{ "two", 2 },
	{ "three", 3 },
};
var dic2 = new Dictionary<string, int>()
{
	["one"] = 1,
	["two"] = 2,
	["three"] = 3,
};

個人的にはC#独自記法ではなく、JSONのように世に認知された記法に近いと嬉しいです。pythonだとこうですね。

 dic1 = {'one' : 1, 'two' : 2, 'three' : 3, }

(Javascriptではこれは辞書ではなくオブジェクトですが、)
将来的にはC#内でJSONがそのまま書けたら嬉しいですが、オブジェクトとしてはdynamicやExpandoObjectで受けるしかないのかな。

5. Enumの省略記法

必ずEnumが来ることが分かっている場所は省略できないかと思ってます。

Console.ForegroundColor = ConsoleColor.Red; //現在の書き方
  
Console.ForegroundColor = .Red; //こう書けたらいいな
Console.ForegroundColor = _.Red; //またはこんなイメージ

特にメソッドの引数を短く書きたい・・。
using staticがあるので実現可能性は低いです。
Proposal discussion: Target-typed static member access #9078

Champion: Name lookup for simple name when target type known #8641

6. |>演算子

Proposal: Pipe-forward operator

F#などにあるパイプライン演算子|>。式の結果を次の式の第一引数として渡す演算子です。

例えば「ファイル名を入力し、SHA1を計算して表示」を1行で書こうとすると現在は

Console.WriteLine(              // コンソールに表示
  BitConverter.ToString(        // 文字列に変換
    SHA1.Create().ComputeHash(  // SHA1を計算
      File.ReadAllBytes(        // ファイルを読み込む
        Console.ReadLine())))); // コンソールからファイル名を入力

ですが、パイプライン演算子を使うと

Console.ReadLine()              // コンソールからファイル名を入力、
|> File.ReadAllBytes()          // そのファイルを読込み、
|> SHA1.Create().ComputeHash()  // SHA1を計算
|> BitConverter.ToString()      // 文字列に変換し
|> Console.WriteLine();         // コンソールに表示

と書けます。
dotnet run file.csが実現したのでスクリプト用途には便利かもしれないですが、C#っぽくないですね。

7. Union

[Proposal]: Unions
[Proposal]: Discriminated Unions #8928

Unionにもいろんなタイプがありますが検討されているのは F#やTypeScript などの判別可能なUnion型(Discriminated Unions)のようです。最近(2025-9-29)のデザインミーティングのノートではどんな文法にするかの案が出ています。

//union構文の案
union Pet(Cat, Dog, Bird);
union Pet[Cat, Dog, Bird];
union Pet(Cat | Dog | Bird);
union Pet(Cat or Dog or Bird);
union Pet allows Cat, Dog, Bird;
 

現行のC#でも、空の基底クラスを使うことで似たことはできるのですが、どうなるのでしょう。

8. ConfigureAwait(false)問題

Proposal: Extension await operator to address scoped ConfigureAwait #8955

AIに、「ConfigureAwait抜けてないか探して」とか、アナライザーでチェックさせたりする方法もありますが、ライブラリなどではConfigureAwait(false)の記述自体がコード内の雑音に見えます。

stephentoub先生はawait演算子のオーバーロードという荒業案も出しています。

internal static class ConfigureAwaitExtensions
{
    internal static ConfiguredTaskAwaitable         operator await   (Task t)          => t.ConfigureAwait(false);
    internal static ConfiguredTaskAwaitable<T>      operator await<T>(Task<T> t)       => t.ConfigureAwait(false);
    internal static ConfiguredValueTaskAwaitable    operator await   (ValueTask vt)    => vt.ConfigureAwait(false);
    internal static ConfiguredValueTaskAwaitable<T> operator await<T>(ValueTask<T> vt) => vt.ConfigureAwait(false);
}

よい案が思いつかないですが

  • csprojに書いて、プロジェクトごと適用
  • 名前空間やクラススコープでConfigureAwait(false)をデフォルトにする文法
  • アノテーション(属性)をクラスに貼ったらawaitを上書きする機能/ソースジェネレータ?

あたりが現実的な気もします。アノテーションは既にFodyにあるようです。

//属性を付与したら
[ConfigureAwait(false)]
public partial class A {
    public async Task TestAsync(string url)
    {
        await _client.GetAsync(url); //ConfigureAwait(false)されるといいな
    }
}

9.ランタイム等

言語機能ではありませんが、@hez2010さんが書かれているRuntime AsyncSatori GCなどのほうが最近は気になります。
これらがデフォルトで有効になったら、と思うとわくわくします。
roslynのLanguage Feature Statusを見るとRuntime Asyncも進んでますね。

15
4
0

Register as a new user and use Qiita more conveniently

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?