はなちるのマイノート

Unityをメインとした技術ブログ。自分らしくまったりやっていきたいと思いますー!

【C#】ジェネリック制約で特定のインターフェースを実装しているとはじかせる方法を考えた

はじめに

前提としてC#のジェネリック制約ではTはIHogeを実装するが、IFugaは実装しないことのようなインターフェースの否定的な制約を記述をすることはできません。

public interface IHoge;
public interface IFuga;

public static class Sandbox
{
    // IHogeを実装している型を引数として受け取れる
    // IFugaを実装しているかどうかは関係ない
    public static bool Execute<T>(T value) where T : IHoge
    {
        return true;
    }
}

ただどうしてもコンパイルエラーで弾きたいなという場面がありまして、その方法を考えてみたのでメモを書き残しておきたいと思います。補足すると仮に適応できたとしてもハックに近いので推奨はしません。

やり方

まず[Obsolete]を活用して、コンパイルエラーにしたい組み合わせを記述します。
learn.microsoft.com

あとはinを活用してオーバーロードを定義すれば完成です。
learn.microsoft.com

public interface IHoge;
public interface IFuga;

public static class Sandbox
{
    // Obsoleteのerrorという引数をtrueにしてコンパイルエラーになるようにする
    [Obsolete("TはIFugaを実装しないこと", true)]
    public static bool Execute<T>(T value) where T : IHoge, IFuga
    {
        return true;
    }
 
    // ジェネリック型制約のみ異なっても、C#ではオーバーロードを定義できない
    // inを付与するとオーバーロードをとして定義できる
    public static bool Execute<T>(in T value) where T : IHoge
    {
        return true;
    }
}

このように記述することで、

  • IHogeIFugaを実装していると前者を利用(コンパイルエラー)
  • IHogeのみを実装していると後者を利用(コンパイルエラーにならない)

という挙動になります。

実際にコンパイルエラーになっている様子

コンパイルエラーの回避方法

明示的にinを付与することでコンパイルエラーを回避することは可能です。

// 明示的にinを指定することでコンパイルエラーにならない
var value = new A();
var x = Sandbox.Execute(in value);

まあこれは意図的でないとやらないと思うので、そこまで気にしなくても良いかもしれません。

inキーワードについて

当然inパラメーター修飾子の有無で参照渡しになるかどうかが変わります。またそもそも読み取り専用でなければ利用できないので注意です。

learn.microsoft.com