📖

.NET Standard 2.0 / .NET Framework でも新機能を使いたい!

2021/12/30に公開

.NET Standard 2.0 でも新機能を使いたい!

そもそも .NET Standard とは?

.NET Standard は、.NET Framework や .NET Core 系などの .NET API の標準です。例えば、.NET Framework 4.6.1 以降と .NET Core 2.0 以降は .NET Standard 2.0 を実装しています。つまり、.NET Standard 2.0 で Class Library を書けば、.NET Framework や .NET Core に両対応になるのです。

何かしらの理由で .NET Framework も対応させたいとなると、.NET Standard 2.0 の Class Library を開発するのが手軽です。(ターゲットに net461;netcoreapp20 などとする方法もあります)

ちなみに、.NET Standard 2.1 を実装した .NET Framework はありません。.NET Framework に対応させるならば、.NET Standard 2.0 でないといけません。

C# / .NET の新機能を使う

C# / .NET は最近毎年リリースされていて、そのたびにこれは便利って機能が投入されています。.NET Standard 2.0 は C# 7.3 をサポートしています。今は C# 10 です。

開発をしていると、この機能便利やなあ使いたいってなりますが、事情があって .NET Framework を捨てられない場合はあきらめることになります。

でも、あきらめなくてもいいんです! すべての新機能が使えないわけではありません。

C# / .NET の新機能は主に次の三つからなります。

  • .NET Runtime で追加
  • C# コンパイラーで追加
  • Class Library で追加

このうち、.NET Runtime が必要な新機能 (Ex: interface のデフォルト実装) はどうしようもありませんが、コンパイラーの対応だけで追加された新機能 (Ex: null 許容参照型) は .NET SDK を新しいものを使うだけです。Class Library はものによっては古い環境用に提供されています。

ライブラリによるサポート

HashCode.Combine()

HashCode.Combine() は、Object.GetHashCode() で利用するためのハッシュ計算用ヘルパー関数です。

よいハッシュ値を計算するのは意外と難しいのです。(面倒なので詳細は割愛しますが)

こんな感じで使います。

public sealed class PostalCode : IEquatable<PostalCode>
{
    public string High { get; }
    public string Low { get; }

    // ...

    public override int GetHashCode()
    {
        return HashCode.Combine(High, Low);
    }
}

.NET Standard 2.0 で使うには、Microsoft.Bcl.HashCode を参照するだけです。

私自身は ReSharper / [Rider] (https://www.jetbrains.com/ja-jp/rider/) にハッシュ計算を自動でやらせていますのでなくてもかまわないのですけどね。(ReSharper / Rider は HashCode.Combine() が使える環境ではそれを使います)

Immutable Collection

System.Collections.Immutable は最近はやりの、イミュータブルなコレクションクラスです。

System.Collections.Immutable を参照するだけで使うことができます。

コンパイラーによるサポート

Runtime を必要としない、コンパイラーのみで提供された新機能も少なくありません。

新機能がコンパイラーによるサポートであれば、.NET SDK をアップデートして、*.csproj ファイルの言語バージョンを明示すれば、.NET Standard 2.0 でも使うことができます。

<PropertyGroup>
    <!-- ... -->
    <TargetFramework>netstandard2.0</TargetFramework>
    <LangVersion>10</LangVersion>
</PropertyGroup>

null 許容参照型

null 許容参照型 は C# 8.0 で入った新機能です。

これはコンパイラーの対応なので、.NET SDK 3.0 以降であれば .NET Standard 2.0 で使えます。

*.csproj で言語バージョンと Nullable を指定するだけです。

<PropertyGroup>
    <!-- ... -->
    <TargetFramework>netstandard2.0</TargetFramework>
    <LangVersion>10</LangVersion>
    <Nullable>enable</Nullable>
</PropertyGroup>

record 型

record 型 は制限がありますが、.NET Standard で使うことができます。

残念ですが、よく例に挙げられている次のコードはコンパイルエラーになります。

public record PostalCode(string High, string Low);

init 専用セッターを内部で利用しているのですが、これが Runtime のサポートを必要とするためです。(多分)

少し面倒ですが、次のようにすれば回避は可能です。

public record PostalCode(string High, string Low)
{
    public string High { get; } = High;
    public string Low { get; } = Low;
}

ファイル スコープ名前空間宣言

ファイル スコープ名前空間宣言は C# 10.0 から利用できる、ちょっとだけうれしい新機能です。

今までは名前空間はブロックで書いていました。

namespace Sample {
    class Hoge {}
}

次のように書けるようになりました。

namespace Sample;

class Hoge {}

インデントが一段浅くなるので、横幅が狭い環境でサイドバイサイドの diff を見るときにありがたいです。

コンパイラーによるサポートなので言語バージョンを指定すれば .NET Stadard 2.0 でも使えます。

ローカル関数

ローカル関数も使えます。

ラムダ式やデレゲートでもいいのですが、ローカル関数は次のように呼び出しコードより後に書けるので、コードを読む時のノイズが減るのがいいですね。

public void Hello()
{
    Show("Hello!");
    
    void Show(string message)
    {
	Console.WriteLine(message);
    }
}

コンパイラーによるサポートなので言語バージョンを指定すれば .NET Stadard 2.0 でも使えます。

まあ、なくてもクラスとして抽出すればいいだけなのですが...

using ステートメント

using ステートメント は C# 8.0 から、ブロックなしが使えるようになりました。

public void Hello()
{
    using var stream = new MemoryStream();
    // ...
}

インデントが一つ浅くなるのがうれしいですね!

switch 式 / パターンマッチング

switch 式パターンマッチングも .NET Standard 2.0 で使うことができます。

例はめんどくさいので...

終わりに

こうしてみると、結構使えるんですよね。紹介はしませんでしたが、非同期ストリームや範囲アクセスも使えるようです。使えない機能を探す方が難しいような気もします。

使えない機能の中で、最も残念なのは interface のデフォルト実装です。(厳密にはデフォルト実装というより、static メンバーなファクトリメソッドを書けるのがうらやましい)

.NET Framework / .NET Standard 2.0 を使っていてもあきらめる必要はないんです! とはいっても、.NET SDK だけでなく、Visual Studio や Rider のバージョンもあたらしいものが必要なんですけどね...

Discussion