例外処理

【C#】InvalidFilterCriteriaExceptionの原因・発生条件と対処法をわかりやすく解説

InvalidFilterCriteriaExceptionはリフレクションでメンバー検索を行う際、フィルター引数が不正なときに発生します。

BindingFlagsの組み合わせミスやデリゲートシグネチャの不一致が主因で、引数を検証し、try‐catchで捕捉してログを残すと安全です。

InvalidFilterCriteriaExceptionとは

例外の定義と位置づけ

InvalidFilterCriteriaException は、C# の System.Reflection 名前空間に属する例外クラスの一つです。

この例外は、リフレクションを使ってクラスや構造体のメンバーを検索する際に、指定されたフィルター条件が無効である場合にスローされます。

主に Typeクラスの FindMembersメソッドで発生することが多いです。

リフレクションは、実行時に型の情報を取得したり操作したりするための強力な機能ですが、メンバーの検索条件を誤って指定すると、InvalidFilterCriteriaException が発生してしまいます。

例えば、メンバーの種類やアクセス修飾子を指定するフィルター条件が間違っている場合や、フィルター用のデリゲートが不正な戻り値を返す場合などが該当します。

この例外は、プログラムの実行中に動的に型情報を扱う際のエラーを示すため、適切に捕捉して処理しないとアプリケーションがクラッシュする原因になります。

したがって、リフレクションを使うコードを書く際には、フィルター条件の妥当性を確認し、例外処理を組み込むことが重要です。

例外クラスの継承関係と主要プロパティ

InvalidFilterCriteriaException は、.NET の例外クラス階層の中で以下のような継承関係を持っています。

  • System.Object
    • System.Exception
      • System.SystemException
        • System.Reflection.InvalidFilterCriteriaException

このように、InvalidFilterCriteriaExceptionSystemException を継承しているため、システムレベルの例外として扱われます。

SystemException は、.NET ランタイムやフレームワークの内部で発生する例外の基底クラスであり、プログラマが直接スローすることはあまりありませんが、リフレクション関連のエラーとしてはこの例外が用いられています。

主なプロパティは以下の通りです。

プロパティ名説明
Message例外の説明メッセージ。既定では「指定されたフィルター条件は無効です。」が設定されています。
InnerException例外の原因となった内部例外があれば格納されます。通常は null のことが多いです。
StackTrace例外が発生した呼び出し履歴を文字列で取得できます。デバッグ時に役立ちます。
HelpLink例外に関連するヘルプファイルやURLを指定できます。通常は設定されていません。
Source例外が発生したアプリケーションやオブジェクトの名前を示します。

InvalidFilterCriteriaException は特別な追加プロパティを持っていませんが、Exceptionクラスの標準的なプロパティを活用して、例外の詳細を把握しやすくなっています。

以下は、例外の基本的な使い方のイメージです。

try
{
    // Typeオブジェクトからメンバーを検索する例
    var type = typeof(SampleClass);
    // 無効なフィルター条件を指定(例:不正なMemberTypes値)
    var members = type.FindMembers((MemberTypes)9999, BindingFlags.Public | BindingFlags.Instance, null, null);
}
catch (InvalidFilterCriteriaException ex)
{
    Console.WriteLine($"例外発生: {ex.Message}");
}

この例では、FindMembersメソッドに存在しない MemberTypes の値を渡しているため、InvalidFilterCriteriaException がスローされます。

例外のメッセージを表示することで、何が問題だったのかを把握できます。

このように、InvalidFilterCriteriaException はリフレクションのフィルター条件が不正な場合に発生する例外であり、例外の継承構造やプロパティを理解しておくことで、適切なエラーハンドリングが可能になります。

発生条件を押さえる

Reflection APIと例外発生の関係

リフレクションAPIは、実行時に型の情報を取得したり操作したりするための機能です。

InvalidFilterCriteriaException は、主にリフレクションのメンバー検索メソッドで、無効なフィルター条件が指定された場合に発生します。

特に、FindMembersメソッドでの使用時に注意が必要です。

FindMembersメソッドの特徴

FindMembersメソッドは、指定した条件に合致するメンバー(メソッド、プロパティ、フィールドなど)を検索するためのメソッドです。

シグネチャは以下のようになっています。

MemberInfo[] FindMembers(
    MemberTypes memberType,
    BindingFlags bindingAttr,
    MemberFilter filter,
    object filterCriteria
);
  • memberType:検索対象のメンバーの種類を指定します。MemberTypes 列挙体の値を使います
  • bindingAttr:アクセス修飾子や静的・インスタンスなどの条件を指定する BindingFlags
  • filter:メンバーを絞り込むためのデリゲート。MemberFilter 型で、メンバーと条件を受け取り、true または false を返します
  • filterCriteriafilter に渡される条件オブジェクト

FindMembers は非常に柔軟ですが、memberType に無効な値を指定したり、filterfilterCriteria の組み合わせが不適切だと、InvalidFilterCriteriaException が発生します。

例えば、memberType に存在しない値を指定すると例外がスローされます。

var type = typeof(string);
var invalidMemberType = (MemberTypes)9999; // 存在しない値
var members = type.FindMembers(invalidMemberType, BindingFlags.Public, null, null);

このコードは InvalidFilterCriteriaException を発生させます。

GetMethodsやGetPropertiesでの事例

GetMethodsGetProperties は、FindMembers よりも使いやすいメソッドですが、内部的には似たようなフィルター処理を行っています。

これらのメソッドでは、BindingFlags の指定ミスや、カスタム属性のフィルター条件の誤りで例外が発生することがあります。

例えば、GetMethods に無効な BindingFlags を渡すと、例外が発生することがあります。

var type = typeof(string);
var invalidFlags = (BindingFlags)0xFFFF; // 無効なフラグの組み合わせ
var methods = type.GetMethods(invalidFlags);

このような場合も、InvalidFilterCriteriaException がスローされる可能性があります。

BindingFlagsの設定ミス

BindingFlags は、メンバーの検索条件を細かく指定するためのフラグ列挙体です。

例えば、PublicNonPublicStaticInstance などがあります。

これらはビットフラグとして組み合わせて使いますが、無効な組み合わせや不適切な値を指定すると例外が発生します。

よくあるミスは以下の通りです。

  • BindingFlags を指定しない(デフォルト値のまま)
  • 存在しないビットを含む値を指定
  • PublicNonPublic の両方を指定しないために検索対象が空になる
  • StaticInstance の両方を指定しないために検索対象が限定されすぎる

特に、FindMembers では BindingFlags の指定が必須であり、適切に設定しないと InvalidFilterCriteriaException が発生します。

var type = typeof(string);
// BindingFlagsを指定しない場合(0のまま)
var members = type.FindMembers(MemberTypes.Method, 0, null, null);

このコードは例外を発生させます。

必ず BindingFlags.Public | BindingFlags.Instance など、検索対象を明示的に指定してください。

フィルターデリゲートの戻り値不整合

FindMembers の第3引数に渡す MemberFilter デリゲートは、MemberInfoobject を受け取り、bool を返す必要があります。

戻り値が true の場合は該当メンバーとして検索結果に含まれ、false の場合は除外されます。

もし、このデリゲートが null でないにもかかわらず、戻り値が bool 以外の型を返すような実装や、例外をスローするようなコードが含まれていると、InvalidFilterCriteriaException が発生します。

以下は正しい MemberFilter の例です。

bool FilterByName(MemberInfo member, object criteria)
{
    return member.Name == (string)criteria;
}

逆に、戻り値が bool 以外の型を返すと例外になります。

object InvalidFilter(MemberInfo member, object criteria)
{
    return member.Name; // boolではないため例外の原因になる
}

また、filternull の場合はフィルター処理がスキップされますが、filter が指定されている場合は必ず正しい戻り値を返すようにしてください。

属性フィルタによる例外シナリオ

リフレクションでメンバーを検索する際に、カスタム属性を条件にフィルターをかけることがあります。

このとき、属性の存在チェックや属性の値を参照するフィルター処理が正しく実装されていないと、InvalidFilterCriteriaException が発生することがあります。

例えば、filter デリゲート内で属性の取得に失敗したり、filterCriteria の型が期待と異なる場合です。

bool AttributeFilter(MemberInfo member, object criteria)
{
    var attrType = criteria as Type;
    if (attrType == null)
        throw new ArgumentException("criteriaはType型である必要があります");
    return member.GetCustomAttributes(attrType, false).Length > 0;
}

このように、filterCriteria の型チェックを行わずにキャストすると、実行時に例外が発生し、結果的に InvalidFilterCriteriaException がスローされることがあります。

また、属性の取得に失敗するケースとして、動的にロードしたアセンブリの型が異なる場合や、属性の型が見つからない場合も注意が必要です。

アセンブリ動的ロード時の注意点

動的にアセンブリをロードしてリフレクションを行う場合、型の解決やフィルター条件の指定に注意が必要です。

特に、異なるバージョンのアセンブリや異なるコンテキストでロードされた型を比較すると、InvalidFilterCriteriaException が発生しやすくなります。

例えば、filterCriteria に渡す型が、実際に検索対象のメンバーが属するアセンブリとは異なるアセンブリからロードされた場合、型の不一致が原因で例外が発生します。

// 動的にアセンブリをロード
var assembly = Assembly.LoadFrom("SomeLibrary.dll");
var type = assembly.GetType("SomeNamespace.SomeClass");
// 別のアセンブリから取得したTypeをfilterCriteriaに渡すと不整合が起きる
var criteriaType = typeof(SomeAttribute); // これが別アセンブリ由来の場合
var members = type.FindMembers(MemberTypes.Method, BindingFlags.Public, AttributeFilter, criteriaType);

このような場合は、アセンブリのロード方法や型の取得方法を統一し、同じコンテキストで型を扱うようにしてください。

そうしないと、InvalidFilterCriteriaException が発生しやすくなります。

よくある原因別チェックリスト

フィルター引数がnull

FindMembersメソッドなどでフィルター用の引数filterCriterianull を渡すこと自体は許容される場合がありますが、フィルターデリゲートMemberFilternull でない場合に、filterCriterianull だと想定外の動作や例外が発生することがあります。

特に、フィルターデリゲート内で filterCriteria をキャストして使用している場合、null のままだと NullReferenceException などが発生し、それが原因で InvalidFilterCriteriaException に繋がることがあります。

以下の例では、filterCriterianull のままキャストしているため、例外が発生します。

bool FilterByName(MemberInfo member, object criteria)
{
    // criteriaがnullの場合、ここで例外が発生する可能性がある
    return member.Name == (string)criteria;
}
var type = typeof(string);
try
{
    var members = type.FindMembers(MemberTypes.Method, BindingFlags.Public, FilterByName, null);
}
catch (InvalidFilterCriteriaException ex)
{
    Console.WriteLine($"例外発生: {ex.Message}");
}

このような場合は、フィルターデリゲート内で null チェックを行うか、filterCriteria に適切な値を渡すようにしてください。

型不一致

filterCriteria の型が、フィルターデリゲート内で期待されている型と異なる場合も InvalidFilterCriteriaException の原因になります。

たとえば、filterCriteriaint を渡しているのに、デリゲート内で string としてキャストしようとすると例外が発生します。

bool FilterByName(MemberInfo member, object criteria)
{
    // string型としてキャストしているが、実際はintが渡されている
    return member.Name == (string)criteria;
}
var type = typeof(string);
try
{
    var members = type.FindMembers(MemberTypes.Method, BindingFlags.Public, FilterByName, 123);
}
catch (InvalidFilterCriteriaException ex)
{
    Console.WriteLine($"例外発生: {ex.Message}");
}

このような型不一致は、実行時にキャスト例外を引き起こし、結果的に InvalidFilterCriteriaException がスローされることがあります。

フィルターデリゲート内で型チェックを行うか、呼び出し元で正しい型を渡すことが重要です。

オーバーロード誤認識

リフレクションでメソッドを検索する際、同じ名前のメソッドが複数存在する場合(オーバーロード)、フィルター条件が不十分だと誤ったメソッドを取得したり、例外が発生したりします。

特に、FindMembersfilterCriteriafilter でメソッドのシグネチャを正確に判別できていないと、無効なフィルター条件として扱われ、InvalidFilterCriteriaException が発生することがあります。

bool FilterByParameterCount(MemberInfo member, object criteria)
{
    if (member is MethodInfo method)
    {
        return method.GetParameters().Length == (int)criteria;
    }
    return false;
}
var type = typeof(SampleClass);
try
{
    // 2つのパラメータを持つメソッドを検索
    var members = type.FindMembers(MemberTypes.Method, BindingFlags.Public, FilterByParameterCount, 2);
}
catch (InvalidFilterCriteriaException ex)
{
    Console.WriteLine($"例外発生: {ex.Message}");
}

この例では、SampleClass に2つのパラメータを持つメソッドが存在しない場合、検索結果は空になりますが、フィルター条件自体は有効です。

しかし、フィルターの実装が不適切であったり、filterCriteria の型が合わない場合は例外が発生します。

オーバーロードの判別には、パラメータの型や数、戻り値の型などを正確に指定することが必要です。

ジェネリック型関連の落とし穴

ジェネリック型を扱う際には、型の制約や推論の失敗によって InvalidFilterCriteriaException が発生しやすくなります。

特にリフレクションでジェネリックメソッドやジェネリック型のメンバーを検索する場合は注意が必要です。

制約違反パターン

ジェネリックメソッドやクラスには型パラメータに対する制約(where句)が設定されていることがあります。

リフレクションでこれらの制約を無視して不正な型を指定すると、例外が発生します。

public class SampleClass<T> where T : class
{
    public void Method(T param) { }
}
var type = typeof(SampleClass<>);
try
{
    // 制約に違反する型を指定してジェネリック型を作成しようとすると例外が発生
    var constructedType = type.MakeGenericType(typeof(int)); // intはclass制約に違反
}
catch (InvalidFilterCriteriaException ex)
{
    Console.WriteLine($"例外発生: {ex.Message}");
}

この例では、Tclass 制約があるにもかかわらず、値型の int を指定しているため例外が発生します。

ジェネリック型の制約を正しく理解し、適切な型を指定することが重要です。

型推論失敗パターン

ジェネリックメソッドの呼び出し時に型推論が失敗すると、リフレクションでのメソッド検索や呼び出し時に例外が発生することがあります。

特に、FindMembers のフィルター条件でジェネリックメソッドを正しく判別できない場合に問題が起きます。

public class SampleClass
{
    public void GenericMethod<T>(T param) { }
}
bool FilterGenericMethod(MemberInfo member, object criteria)
{
    if (member is MethodInfo method)
    {
        return method.IsGenericMethod && method.Name == (string)criteria;
    }
    return false;
}
var type = typeof(SampleClass);
try
{
    var members = type.FindMembers(MemberTypes.Method, BindingFlags.Public, FilterGenericMethod, "GenericMethod");
}
catch (InvalidFilterCriteriaException ex)
{
    Console.WriteLine($"例外発生: {ex.Message}");
}

この例では、ジェネリックメソッドをフィルターで判別していますが、filterCriteria の型やフィルターの実装が不適切だと例外が発生します。

ジェネリックメソッドの判別には、IsGenericMethodプロパティや型パラメータの情報を正しく扱う必要があります。

再現コードスニペット集

最小構成サンプル

InvalidFilterCriteriaException が発生する最もシンプルな例を示します。

組み込みの Type.FilterNameIgnoreCase デリゲート(メンバー名を大文字小文字を区別せず比較するビルトインの MemberFilter)を使い、文字列を期待するところにあえて数値を渡すことで InvalidFilterCriteriaException を発生させています。

using System;
using System.Reflection;

class Program
{
    static void Main()
    {
        var type = typeof(string);
        try
        {
            // Built-in の MemberFilter(Type.FilterNameIgnoreCase) は、
            // filterCriteria に string 型を期待している。
            object invalidCriteria = 1234;  // string ではない
            var members = type.FindMembers(
                memberType: MemberTypes.Method,
                bindingAttr: BindingFlags.Public | BindingFlags.Static,
                filter: Type.FilterNameIgnoreCase,
                filterCriteria: invalidCriteria
            );
        }
        catch (InvalidFilterCriteriaException ex)
        {
            Console.WriteLine($"例外発生: {ex.GetType().Name} — {ex.Message}");
        }
    }
}
例外発生: InvalidFilterCriteriaException — A String must be provided for the filter criteria.

このコードは、MemberTypes に定義されていない値を指定しているため、InvalidFilterCriteriaException がスローされます。

FindMembersmemberType は必ず有効な MemberTypes の値を指定してください。

BindingFlags複合使用例

BindingFlags は複数のフラグをビット単位で組み合わせて使いますが、フィルター デリゲートを指定して、そのデリゲートが期待する型と異なる filterCriteria を渡す例外が発生します。

は複数の BindingFlags をビット単位で組み合わせつつ、あえて文字列を期待するフィルタ―に数値を渡して InvalidFilterCriteriaException を発生させるサンプルです。

using System;
using System.Reflection;

class Program
{
    static void Main()
    {
        var type = typeof(string);
        try
        {
            // ■ 複合した BindingFlags の例
            //    - Public/NonPublic: 公開/非公開メンバー両方を検索
            //    - Instance/Static: インスタンス/静的メンバー両方を検索
            var flags = BindingFlags.Public
                      | BindingFlags.NonPublic
                      | BindingFlags.Instance
                      | BindingFlags.Static;

            // ■ フィルター デリゲート(名前比較を大文字小文字無視で行う組み込み)
            MemberFilter filter = Type.FilterNameIgnoreCase;

            // ■ 本来 string を期待するところに int を渡す → 例外発生
            object invalidCriteria = 2025;

            // メソッドとプロパティをまとめて検索
            var members = type.FindMembers(
                memberType: MemberTypes.Method | MemberTypes.Property,
                bindingAttr: flags,
                filter: filter,
                filterCriteria: invalidCriteria
            );
        }
        catch (InvalidFilterCriteriaException ex)
        {
            Console.WriteLine($"例外発生: {ex.GetType().Name} — {ex.Message}");
        }
    }
}
例外発生: InvalidFilterCriteriaException — A String must be provided for the filter criteria.

BindingFlags は公式ドキュメントにある定義済みのフラグを組み合わせて使う必要があります。

無効なビットを含む値を指定すると、InvalidFilterCriteriaException が発生します。

カスタム属性での発生例

カスタム属性を条件にメンバーを検索する際、filterCriteria の型がフィルターデリゲートの期待と異なる場合や、フィルターデリゲート内での実装に問題があると、InvalidFilterCriteriaException がスローされます。

以下の例では、本来 Type を渡すべき filterCriteria に文字列を指定することで、型ミスマッチによる例外発生を示しています。

using System;
using System.Reflection;

[AttributeUsage(AttributeTargets.Method)]
class SampleAttribute : Attribute { }

class SampleClass
{
    [Sample]
    public void MarkedMethod() { }

    public void UnmarkedMethod() { }
}

class Program
{
    // カスタムフィルター:criteria に Type 型を期待
    static bool AttributeFilter(MemberInfo member, object criteria)
    {
        // criteria が Type でなければ ArgumentException をスロー
        if (!(criteria is Type attrType))
            throw new ArgumentException("filterCriteria は Type 型で指定してください。");

        // 指定された属性が付いているメソッドだけを true
        return member.IsDefined(attrType, inherit: false);
    }

    static void Main()
    {
        var type = typeof(SampleClass);
        try
        {
            // ■ filterCriteria に string を渡して型ミスマッチを起こす
            var members = type.FindMembers(
                memberType: MemberTypes.Method,
                bindingAttr: BindingFlags.Public | BindingFlags.Instance,
                filter: AttributeFilter,
                filterCriteria: "SampleAttribute"  // 本来は typeof(SampleAttribute)
            );
        }
        catch (InvalidFilterCriteriaException ex)
        {
            // デリゲート呼び出し時に criteria の型不正で発生
            Console.WriteLine($"例外発生: {ex.GetType().Name} — {ex.Message}");
        }
        catch (ArgumentException ex)
        {
            // フィルター内部で投げた ArgumentException を捕捉
            Console.WriteLine($"フィルター内例外: {ex.Message}");
        }
    }
}
フィルター内例外: filterCriteria は Type 型で指定してください。

この例では、filterCriteriaType型ではなく string を渡しているため、フィルターデリゲート内で ArgumentException がスローされ、それが原因で InvalidFilterCriteriaException が発生することがあります。

例外の原因を特定しやすくするために、フィルター内での型チェックと例外処理を適切に行うことが重要です。

例外メッセージを読み解く

Messageプロパティの内容

InvalidFilterCriteriaExceptionMessageプロパティには、例外の原因を示す説明文が格納されています。

既定のメッセージは「指定されたフィルター条件は無効です。」となっており、これはフィルター条件に誤りがあることを示しています。

このメッセージは非常にシンプルであるため、具体的な原因までは示していません。

したがって、単にこのメッセージだけを見て原因を特定するのは難しい場合があります。

例えば、MemberTypes の不正な値、BindingFlags の誤設定、フィルターデリゲートの戻り値不整合など、さまざまな原因が考えられます。

例外発生時には、Messageプロパティをまず確認し、フィルター条件に問題があることを認識したうえで、他の情報(InnerException やスタックトレース)を参照して詳細を調査することが重要です。

InnerException確認ポイント

InnerExceptionプロパティは、例外の根本原因となった内部例外を保持しています。

InvalidFilterCriteriaException 自体はフィルター条件の不正を示す例外ですが、内部で発生した別の例外が原因となっている場合があります。

例えば、フィルターデリゲート内でキャスト例外や ArgumentExceptionNullReferenceException が発生し、それが InvalidFilterCriteriaException の原因となることがあります。

このような場合、InnerException に元の例外情報が格納されているため、詳細な原因を特定する手がかりになります。

以下のように、InnerException を確認して例外の詳細をログに出力すると原因解析がしやすくなります。

catch (InvalidFilterCriteriaException ex)
{
    Console.WriteLine($"例外メッセージ: {ex.Message}");
    if (ex.InnerException != null)
    {
        Console.WriteLine($"内部例外: {ex.InnerException.GetType().Name} - {ex.InnerException.Message}");
    }
}

InnerExceptionnull の場合は、直接的にフィルター条件の不正が原因であることが多いですが、null でない場合は内部例外の内容を重点的に調査してください。

スタックトレース解析

スタックトレースは、例外が発生した時点の呼び出し履歴を示す文字列情報です。

InvalidFilterCriteriaException のスタックトレースを解析することで、どのメソッドのどの行で例外が発生したかを特定できます。

特に、リフレクションのどの処理で例外が発生したのか、呼び出し元のコードのどの部分が問題なのかを把握するのに役立ちます。

Visual Studio などの開発環境では、例外発生時にスタックトレースが自動的に表示されるため、該当箇所のソースコードをすぐに確認できます。

スタックトレースの中で、FindMembersGetMethodsGetProperties といったリフレクション関連のメソッド呼び出しが含まれているかをチェックしてください。

また、スタックトレースの中で自分が実装したフィルターデリゲートのメソッド名が含まれている場合は、そのメソッド内の処理に問題がある可能性が高いです。

例えば、型キャストや条件判定の部分を重点的に見直すとよいでしょう。

スタックトレースの例:

at System.Reflection.RuntimeType.FindMembers(MemberTypes memberType, BindingFlags bindingAttr, MemberFilter filter, Object filterCriteria)
at SampleNamespace.Program.Main() in C:\Projects\Sample\Program.cs:line 25

この例では、RuntimeType.FindMembers 内で例外が発生し、呼び出し元の Program.Main の25行目が問題の起点であることがわかります。

これにより、該当コードのフィルター条件や引数を重点的に確認できます。

スタックトレースを活用して、例外発生箇所の特定と原因調査を効率的に行いましょう。

デバッグ手法

Visual Studioの例外設定

Visual Studioでは、例外が発生したタイミングで自動的にデバッガを停止させる設定が可能です。

InvalidFilterCriteriaException のような例外を効率よく検出し、原因を特定するために例外設定を活用しましょう。

手順は以下の通りです。

  1. Visual Studioのメニューから「デバッグ」→「例外設定」を開きます。
  2. 「例外設定」ウィンドウで「Common Language Runtime Exceptions(共通言語ランタイム例外)」を展開します。
  3. System.Reflection.InvalidFilterCriteriaException を探し、チェックボックスをオンにします。もしリストにない場合は、「追加」ボタンから例外名を入力して追加します。
  4. これで、InvalidFilterCriteriaException がスローされた瞬間にデバッガが停止し、例外発生箇所のコードを直接確認できます。

この設定により、例外が発生しても見逃すことなく、スタックトレースやローカル変数の状態を詳細に調査できます。

特にリフレクションを多用するコードでは、どのフィルター条件が問題かを素早く特定するのに役立ちます。

デバッガージャンプ

例外発生時にVisual Studioのデバッガが停止したら、スタックトレースを活用して問題の発生箇所にジャンプします。

InvalidFilterCriteriaException はリフレクションの内部処理で発生することが多いため、呼び出し元の自分のコードに戻ることが重要です。

デバッガの「コールスタック」ウィンドウを開き、リフレクション関連のフレーム(例:RuntimeType.FindMembers)から一つずつ上のフレームに移動していきます。

自分が実装したフィルターデリゲートや、FindMembers を呼び出している箇所を特定してください。

該当箇所に到達したら、変数の値や引数の内容をウォッチウィンドウやローカル変数ウィンドウで確認します。

特に以下の点をチェックしましょう。

  • memberTypebindingAttr の値が正しいか
  • フィルターデリゲートの実装が期待通りか
  • filterCriteria の型や値が適切か

これらを確認しながら、問題の原因となっているフィルター条件を特定し、修正に繋げます。

ログ出力量増での絞り込み

例外の原因が複雑でデバッガだけでは特定しにくい場合は、ログ出力を増やして絞り込みを行います。

特にリフレクションのフィルター処理内にログを埋め込むことで、どの条件で例外が発生しているかを詳細に追跡できます。

具体的には、以下のようなポイントでログを出力します。

  • FindMembersGetMethods を呼び出す直前の引数内容memberTypebindingAttrfilterCriteria
  • フィルターデリゲートの呼び出し時に渡される MemberInfo の名前や型
  • フィルターデリゲートの戻り値や例外発生の有無
  • 例外発生時のスタックトレースやメッセージ

ログはコンソール出力やファイル出力、あるいは専用のロギングフレームワーク(例:NLogSerilog)を使うと便利です。

以下は簡単なログ出力例です。

bool FilterWithLogging(MemberInfo member, object criteria)
{
    Console.WriteLine($"フィルター呼び出し: Member={member.Name}, Type={member.MemberType}");
    try
    {
        bool result = (member.Name == (string)criteria);
        Console.WriteLine($"フィルター結果: {result}");
        return result;
    }
    catch (Exception ex)
    {
        Console.WriteLine($"フィルター内例外: {ex.GetType().Name} - {ex.Message}");
        throw;
    }
}

このようにログを増やすことで、どのメンバーや条件で例外が発生しているかを特定しやすくなります。

ログの内容をもとにフィルター条件の見直しや引数の修正を行い、問題解決に繋げてください。

安全なコーディングパターン

事前バリデーションで例外予防

リフレクションを使ったメンバー検索で InvalidFilterCriteriaException を防ぐためには、フィルター条件や引数の妥当性を事前にチェックすることが重要です。

例外が発生する前に不正な値を検出し、適切に処理することで安定した動作を実現できます。

nullチェックと型チェック

FindMembers の引数のうち、特に filter(フィルターデリゲート)と filterCriterianull であるか、または期待される型かどうかを必ずチェックしましょう。

filternull の場合はフィルター処理がスキップされますが、filter が指定されている場合は filterCriteria の型が合っているかを確認しないと例外の原因になります。

以下は、filterCriteria の型チェックを行う例です。

bool SafeFilter(MemberInfo member, object criteria)
{
    if (criteria == null)
        return false;
    if (!(criteria is string targetName))
        throw new ArgumentException("filterCriteriaはstring型である必要があります");
    return member.Name == targetName;
}

呼び出し側でも、filterCriterianull や不正な型を渡さないように注意してください。

object filterCriteria = "MethodName"; // 期待される型に合わせる
if (filterCriteria == null || !(filterCriteria is string))
{
    Console.WriteLine("filterCriteriaが不正です。");
    return;
}

このように、null チェックと型チェックを組み合わせることで、例外発生のリスクを減らせます。

BindingFlagsの定数化

BindingFlags は複数のフラグを組み合わせて使いますが、毎回手動でビット演算を行うとミスが起きやすいです。

そこで、よく使う組み合わせを定数として定義し、再利用することでミスを防止できます。

const BindingFlags PublicInstanceFlags = BindingFlags.Public | BindingFlags.Instance;
const BindingFlags NonPublicStaticFlags = BindingFlags.NonPublic | BindingFlags.Static;

こうすることで、コードの可読性も向上し、誤ったフラグ指定による InvalidFilterCriteriaException の発生を防げます。

try-catchの最小化

例外処理は必要ですが、過剰に広範囲で try-catch を使うと、問題の特定が難しくなったり、パフォーマンスに悪影響を与えたりします。

InvalidFilterCriteriaException の発生が予想される箇所だけに絞って例外処理を行い、他の部分は正常系のコードとしてシンプルに保つことが望ましいです。

例えば、FindMembers の呼び出し部分だけを try-catch で囲み、例外発生時にログ出力や代替処理を行う形が良いでしょう。

try
{
    var members = type.FindMembers(MemberTypes.Method, PublicInstanceFlags, SafeFilter, "TargetMethod");
    // 正常処理
}
catch (InvalidFilterCriteriaException ex)
{
    Console.WriteLine($"フィルター条件エラー: {ex.Message}");
    // 代替処理やリカバリ
}

このように、例外処理の範囲を限定することで、問題の切り分けや保守性が向上します。

フィルターデリゲートの汎用化

フィルターデリゲートは、特定の条件に依存しすぎると再利用性が低くなり、誤った条件指定による例外リスクも高まります。

汎用的で安全なフィルターを作成し、必要に応じてパラメータを渡す形にすると良いでしょう。

例えば、名前でフィルターする汎用的なデリゲートを用意し、条件は引数で渡す方法です。

Func<string, MemberFilter> CreateNameFilter = (targetName) =>
{
    return (member, criteria) =>
    {
        if (criteria == null || !(criteria is string name))
            return false;
        return member.Name == name;
    };
};
var filter = CreateNameFilter("TargetMethod");
var members = type.FindMembers(MemberTypes.Method, PublicInstanceFlags, filter, "TargetMethod");

このように、フィルターのロジックを外部化し、型チェックや例外処理を組み込むことで、誤った条件指定による例外発生を抑制できます。

また、複数の条件を組み合わせる場合も、個別のフィルターを組み合わせて使うことで、コードの見通しが良くなり安全性が高まります。

実運用での対策フロー

例外発生時ログ項目

InvalidFilterCriteriaException が発生した際に、原因を迅速に特定するためには適切なログ出力が欠かせません。

ログには以下の項目を含めると効果的です。

項目名内容例・説明
例外メッセージ例外の Message プロパティの内容
例外の種類例外クラス名InvalidFilterCriteriaException
InnerException情報内部例外があればその種類とメッセージ
スタックトレース例外発生箇所の呼び出し履歴
フィルター条件FindMembers の引数MemberTypesBindingFlagsfilterCriteria
フィルターデリゲート名使用しているフィルターデリゲートのメソッド名
実行環境情報OS、.NETランタイムバージョン、アプリケーションバージョンなど

これらの情報をログに残すことで、どの条件や環境で例外が発生したかを詳細に把握できます。

特にフィルター条件の値は、誤った指定が原因であることが多いため必ず記録してください。

ユーザー影響抑制リカバリ

例外が発生してもユーザー体験を損なわないように、リカバリ処理を組み込むことが重要です。

InvalidFilterCriteriaException は主に開発時の設定ミスや動的条件の誤りが原因なので、以下のような対策が考えられます。

  • デフォルト条件へのフォールバック

フィルター条件が不正な場合は、例外をキャッチして安全なデフォルト条件で再検索を試みる。

  • ユーザーへのエラーメッセージ表示の抑制

例外内容を直接ユーザーに見せず、ログに記録した上で一般的なエラーメッセージを表示します。

  • 機能の限定的な停止

例外が発生した機能だけを一時的に無効化し、他の機能は継続して利用可能にします。

  • 再試行ロジックの実装

一時的な条件不整合の場合は、一定回数の再試行を行い、成功すれば処理を継続します。

これらのリカバリを組み込むことで、例外発生時のシステムダウンやユーザーの混乱を防げます。

CI/CDパイプラインでの自動テスト

継続的インテグレーション(CI)や継続的デリバリー(CD)環境において、InvalidFilterCriteriaException の発生を未然に防ぐために自動テストを充実させることが効果的です。

ユニットテストでの例外検証

フィルター条件やリフレクション処理を含むメソッドに対して、例外が発生しないことを確認するユニットテストを作成します。

特に以下のポイントをテストケースに含めるとよいでしょう。

  • 有効な MemberTypesBindingFlags の組み合わせで正常にメンバーが取得できること
  • 無効な MemberTypesBindingFlags を渡した場合に InvalidFilterCriteriaException がスローされること
  • フィルターデリゲートに対して、null や不正な型の filterCriteria を渡した場合の挙動
  • ジェネリック型やカスタム属性を使ったフィルター条件の正常動作
[TestMethod]
public void FindMembers_WithInvalidMemberType_ThrowsInvalidFilterCriteriaException()
{
    var type = typeof(string);
    Assert.ThrowsException<InvalidFilterCriteriaException>(() =>
    {
        type.FindMembers((MemberTypes)9999, BindingFlags.Public, null, null);
    });
}

このように例外発生を期待するテストを含めることで、誤った条件指定を早期に検出できます。

リグレッションテスト追加

既存のリフレクション関連機能に対して、InvalidFilterCriteriaException が再発しないことを保証するためにリグレッションテストを追加します。

過去に問題があった条件やパターンをテストケースとして残し、将来的な変更で同様の問題が起きないようにします。

リグレッションテストは、CIパイプラインで自動実行されることで、コードの品質維持に大きく貢献します。

特に複雑なフィルター条件や動的な型操作を行う部分は重点的にカバーしましょう。

これらの対策を組み合わせることで、InvalidFilterCriteriaException の発生を抑えつつ、万が一発生しても迅速に対応できる体制を構築できます。

Alternative APIの検討

LINQによる代替検索

リフレクションの FindMembersメソッドは強力ですが、フィルター条件の指定ミスで InvalidFilterCriteriaException が発生しやすいという課題があります。

これを回避するために、GetMembersGetMethods などで一旦メンバーを取得し、その後にLINQを使って絞り込む方法が有効です。

LINQを使うことで、フィルター条件を柔軟かつ明示的に記述でき、型安全性も高まります。

例えば、名前や属性、アクセス修飾子などを条件に絞り込む場合、以下のように書けます。

using System;
using System.Linq;
using System.Reflection;
class Program
{
    static void Main()
    {
        var type = typeof(string);
        // BindingFlagsでpublicかつインスタンスメソッドを取得し、「Get」で始まるメソッドを取得
        var methods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance)
                          .Where(m => m.Name.StartsWith("Get"));
        foreach (var method in methods)
        {
            // メソッド名と戻り値の型名を表示する
            Console.WriteLine($"{method.Name} : {method.ReturnType.Name}");
        }
    }
}
GetHashCode : Int32
GetHashCode : Int32
GetPinnableReference : Char&
GetEnumerator : CharEnumerator
GetTypeCode : TypeCode
GetType : Type

この方法は、FindMembers のように複雑なフィルター条件を一括で指定する必要がなく、個別に条件を組み合わせて柔軟に検索できます。

また、LINQのクエリ式やラムダ式を使うことで、可読性も向上します。

Typeクラスの名前検索メソッド活用

Typeクラスには、特定の名前のメンバーを直接取得するためのメソッドが用意されています。

例えば、GetMethodGetPropertyGetField などです。

これらは名前を指定して単一のメンバーを取得するため、フィルター条件の誤りによる例外発生リスクが低くなります。

var type = typeof(string);
var method = type.GetMethod("Substring", new Type[] { typeof(int), typeof(int) });
if (method != null)
{
    Console.WriteLine($"メソッド名: {method.Name}");
}
else
{
    Console.WriteLine("該当メソッドは存在しません。");
}
メソッド名: Substring

このように、名前とパラメータ型を指定してメソッドを取得できるため、FindMembers のような複雑なフィルター条件を使わずに済みます。

単純な名前検索や特定のシグネチャを持つメンバーの取得にはこちらのメソッドを活用すると安全です。

ソースジェネレーター活用

C# 9.0以降で利用可能なソースジェネレーターを活用すると、コンパイル時に型情報を解析し、リフレクションを使わずに安全かつ高速にメンバー情報を取得できます。

これにより、実行時の InvalidFilterCriteriaException の発生を根本的に防止できます。

ソースジェネレーターは、ビルド時にコードを自動生成する仕組みで、例えば特定の属性が付いたメンバーの一覧を生成したり、型のメソッド呼び出しコードを自動生成したりできます。

以下は簡単なイメージです。

  • 属性を付けたメンバーを検出し、その名前や型情報を静的クラスに生成
  • 実行時は生成されたコードを直接呼び出すため、リフレクション不要
  • 型安全かつ高速なアクセスが可能

実際の導入には、ソースジェネレーターの作成やNuGetパッケージの利用が必要ですが、リフレクションの例外問題を回避しつつパフォーマンス向上も期待できるため、大規模プロジェクトやパフォーマンスが重要な場面で有効です。

まとめると、LINQや名前検索メソッドで柔軟かつ安全にメンバーを絞り込み、さらに高度な対策としてソースジェネレーターを活用することで、InvalidFilterCriteriaException の発生リスクを大幅に減らせます。

まとめ

この記事では、C#のInvalidFilterCriteriaExceptionが発生する原因や条件、対処法をわかりやすく解説しました。

主にリフレクションのFindMembersメソッドで無効なフィルター条件を指定した際に起こる例外で、BindingFlagsの誤設定やフィルターデリゲートの戻り値不整合が代表的な原因です。

例外メッセージやスタックトレースの読み解き方、Visual Studioでのデバッグ手法、事前バリデーションによる予防策も紹介しています。

さらに、LINQや名前検索メソッド、ソースジェネレーターを使った代替手段も提案し、実運用でのログ管理や自動テストの重要性も説明しました。

これらを理解し実践することで、例外発生を抑えつつ安定したリフレクション処理が可能になります。

関連記事

Back to top button