LoginSignup
1
0

More than 3 years have passed since last update.

private な Generics Type のインスタンス化

Posted at

はじめに

Unit Test を書く際に private や internal なクラスのインスタンスを作りたくなることがあります。
(internal については自分で書いたライブラリであれば InternalsVisibleTo でテスト用のプロジェクトを指定すればテストが簡単になる)

この時、Generics ではない普通のクラスであれば AssemblyGetType メソッドにクラス名を指定してあげることで型情報を取得することができます。
しかし Generics の場合、型をパラメータとして与えることができるため、型情報を取得するときに名前の指定方法を少し気を付ける必要があるのでその情報をまとめます。

なおこのサンプルで使用する Unit Test のライブラリは xUnit を使用していることとします。

方法

例えば Sample.csproj の Sample.cs に以下のような Sample クラスが定義されていたとします。

Foo.cs
namespace Sample
{
    class Foo<TBar, TBaz>
    {
        public TBar Bar { get; set; }
        public TBaz Baz { get; set; }
    }
}

このクラスをテストプロジェクトである Sample.Tests.csproj でインスタンス化するには以下のようにします。

FooTest.cs
using System:
using System.Reflection;
using Xunit;

namespace Sample.Tests
{
    public class Bar
    {
    }

    public class Baz
    {
    }

    public class FooTest
    {
        [Fact]
        public void Test()
        {
            // Hoge は Sample.csproj で public なクラス
            // テスト対象の Assembly を取得する方法はいくつかあるがライブラリのテストであれば
            // プロジェクトの参照を持っているだろうし public なクラスもあるはずなので GetAssembly で取得するのが楽
            var asm = Assembly.GetAssembly(typeof(Hoge));

            // Generics の場合はパラメータとして与えられた型の個数を「`」の後に指定する
            // そして具体的に Generics のパラメータにどのような型を指定するかは
            // 型の最後に[[{型の名前1}],[{型の名前2}],...,[{型の名前n}]]のように指定する
            var type = asm.GetType("Sample.Foo`2[[Sample.Tests.Bar, Sample.Tests],[Sample.Tests.Bar, Sample.Tests]]");

            // 取得した type を使用して CreateInstance でインスタンスを作成する
            var instance = Activator.CreateInstance(type);

            Assert.NotNull(instance);
        }
    }
}

また、 type を取得するには以下のような方法もあります。

var type = asm.GetType("Sample.Foo`2").MakeGenericType(typeof(Bar), typeof(Baz));

この方法は Generics で指定する型のパラメータがテストプロジェクトからアクセスできる場合に有効です。
Intellisense や IDE のリファクタリング機能が作用するので使用できるのであればこちらのほうがおすすめです。

Foo が Nested Type の場合

Foo が別の Generics Type の Nested Type の場合もあるかもしれません。

namespace Sample
{
    class Qux<TQuux>
    {
        private class Foo<TBar,TBaz>
        {
            public TBar Bar { get; set; }
            public TBaz Baz { get; set; }
            public TQuux Quux { get; set; }
        }
    }
}

この場合に type を取得する場合は以下のようにします。

// Nested Type の場合 Declaring Type の後に「+」を付けてそのあとに名前を指定します
// Generics の具体的な型の指定は Declaring Type も Nested Type もまとめて配列で指定します
var type = asm.GetType("Sample.Qux`1+Foo`2[[Sample.Tests.Quux, Sample.Tests],[Sample.Tests.Bar, Sample.Tests],[Sample.Tests.Baz, Sample.Tests]]");

または

var type = asm.GetType("Sample.Qux`1+Foo`2").MakeGenericType(typeof(Quux), typeof(Bar), typeof(Baz));

このように private な Generics 型でもインスタンス化できるので Unit Test で今までテストができていなかったところもテストできるようになります。

Sample Source

今回のサンプルを GitHub に上げました。
generics-type-instance-creating-sample

1
0
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
1
0