【问题标题】:How to use switch statement to match against passed generic type and return object[]如何使用 switch 语句匹配传递的泛型类型并返回对象 []
【发布时间】:2020-12-04 09:37:37
【问题描述】:

我是一个有一点编程知识的测试人员,但是我不能让它工作。

我正在为我的集成测试创建一个基类 (BaseIntegrationTest.cs)。它使用泛型来接受特定测试类所针对的DbContextRepository 类型。基类设置数据库上下文和连接,并创建适当的存储库/控制器类的实例。

    class BaseIntegrationTest <TContext, TRepo> where TContext : DbContext where TRepo : BaseRepository
    {
        ...

    }

除了 DbContextRepository 类型对于每个测试类可能是唯一的,我们还必须确定目标 Repository 类的 DatabaseConnectionString 和构造函数的签名(作为发送到构造函数的参数数量)可能会有所不同)。

我根据传递的泛型类型确定这些值。 TContext 定义我使用哪个 DbConnectionString,TRepo 定义要使用哪个 Repository 构造函数签名。为了定义这些东西,我添加了一个私有静态内部类GenericTypeHelper。我的BaseIntegrationTest 类看起来像这样......

...

namespace OurProduct.Tests.IntegrationTests
{
    class BaseIntegrationTest <TContext, TRepo> where TContext : DbContext where TRepo : BaseRepository
    {
        ...

        protected Dictionary<string, string> LocalDbConfig = new Dictionary<string, string>();
        protected TRepo Repository;

        [OneTimeSetUp]
        public void SetUp()
        {
            DefineLocalDbConfigs();
            SetupNewLocalDB();
            Repository = SetupRepository<TContext, TRepo>();
        }

        [OneTimeTearDown]
        public void TearDown()
        {
            TearDownLocalDB();
        }

        private void DefineLocalDbConfigs()
        {
            ...
            // define LocalDbConfig dictionary and add db configs
            ...
        }

        private void SetupNewLocalDB()
        {
            ...
            // set up LocalDb databases
            ...
        }

        public RepoType SetupRepository<DbContextType, RepoType>() where DbContextType : DbContext where RepoType : BaseRepository
        {
            var configBuilder = new ConfigurationBuilder();
            configBuilder.AddInMemoryCollection(LocalDbConfig);
            var configuration = configBuilder.Build();

            var dbBuilder = new DbContextOptionsBuilder<DbContextType>();
            dbBuilder.UseSqlServer(configuration.GetConnectionString(GenericTypeHelper.DbConnStringForDbContext[typeof(DbContextType)]));

            var context = (DbContextType)Activator.CreateInstance(typeof(DbContextType), new object[] { dbBuilder.Options });
            var logger = Mock.Of<ILogger<RepoType>>();
            var clock = new SystemClock();

            var commonLogger = Mock.Of<ILogger<CommonRepository>>();
            var commonRepo = new CommonRepository(configuration, commonLogger, clock);

            var repoConstrParamObjArr = GenericTypeHelper.GetRepoConstrParamObjArr(context, logger, clock, commonRepo);
            return (RepoType)Activator.CreateInstance(typeof(RepoType), repoConstrParamObjArr);
        }

        private void TearDownLocalDB()
        {
            foreach (var dbConnDefn in LocalDbConfig)
            {
                ...
                // open, set offline, close, dispose database
                ...
            }
        }

        private static class GenericTypeHelper
        {
            internal static readonly Dictionary<Type, string> DbConnStringForDbContext = new Dictionary<Type, string>
            {
                { typeof(DataContextA), "DB1" },
                { typeof(DataContextB), "DB2" },
                { typeof(DataContextC), "DB3" },
                { typeof(DataContextD), "DB1" },
                { typeof(DataContextE), "DB2" },
                { typeof(DataContextF), "DB3" }
            };

            internal static object[] GetRepoConstrParamObjArr<DbContextType, RepoType>(
                    DbContextType context,
                    ILogger<RepoType> logger,
                    SystemClock clock,
                    CommonRepository commonRepo)
                        where DbContextType : DbContext 
                        where RepoType : BaseRepository
                => typeof(RepoType) switch
                {
                    // these give compile error... CS0150: A constant value is expected
                    typeof(DataRepositoryA) => new object[] { context, logger, clock, commonRepo },
                    typeof(DataRepositoryB) => new object[] { context, logger, clock },
                    // these give compile error... CS8121: An expression of type 'Type' cannot be handled by a pattern of type 'DataRepositoryC'
                    DataRepositoryC         => new object[] { context, logger, clock, commonRepo },
                    DataRepositoryD         => new object[] { context, logger, clock },
                    _ => new object[] { }
                };

        }

    }
}

这个内部类给了我编译错误(如 cmets 中所述),我尝试使用 switch 语句将通用 RepoType 与列出的可能的存储库类类型进行比较,这些类型将被传递到 BaseIntegrationTest。有什么方法可以使用干净的开关功能来匹配通用类型吗?

【问题讨论】:

  • RepoType 是一个泛型类型参数,而不是Type 类的实例(在正常约定下,它会被称为TRepo 或类似的名称以明确这一点)。 switch 对象实例上的运算符,因此您需要编写 switch (typeof(RepoType))。但是,switch 语句的 arm 必须是常量表达式,而 typeof(...) 不是常量,因此您不能使用 switch 语句/表达式。您必须使用 if/else if 链,或使用 Dictionary&lt;Type, object[]&gt;

标签: c# generics types switch-statement


【解决方案1】:

虽然@Matthew Watson 的回答(在@The General 的帮助下)很聪明并且解决了我的编译问题,但它并没有按预期运行。 default(RepoType) 为基本类型返回正确类型的预期默认值,但对于非基本类型,它返回 null。这意味着无论我一般传入哪个RepoType,switch 语句都匹配默认的_ =&gt; new object[] { }

不过,我确实通过使用 nameof() 找到了适合我需要的解决方案。

原始代码给我编译错误...

    internal static object[] GetRepoConstrParamObjArr<DbContextType, RepoType>(
            DbContextType context,
            ILogger<RepoType> logger,
            SystemClock clock,
            CommonRepository commonRepo)
                where DbContextType : DbContext 
                where RepoType : BaseRepository
        => typeof(RepoType) switch
        {
            // these give compile error... CS0150: A constant value is expected
            typeof(DataRepositoryA) => new object[] { context, logger, clock, commonRepo },
            typeof(DataRepositoryB) => new object[] { context, logger, clock },
            // these give compile error... CS8121: An expression of type 'Type' cannot be handled by a pattern of type 'DataRepositoryC'
            DataRepositoryC         => new object[] { context, logger, clock, commonRepo },
            DataRepositoryD         => new object[] { context, logger, clock },
            _ => new object[] { }
        };

按预期编译和运行的解决方案...

    internal static object[] GetRepoConstrParamObjArr<DbContextType, RepoType>(
            DbContextType context,
            ILogger<RepoType> logger,
            SystemClock clock,
            CommonRepository commonRepo)
                where DbContextType : DbContext 
                where RepoType : BaseRepository
        => typeof(RepoType).Name switch
        {
            nameof(DataRepositoryA) => new object[] { context, logger, clock, commonRepo },
            nameof(DataRepositoryB) => new object[] { context, logger, clock },
            nameof(DataRepositoryC) => new object[] { context, logger, clock, commonRepo },
            nameof(DataRepositoryD) => new object[] { context, logger, clock },
            _                       => new object[] { }
        };

【讨论】:

    猜你喜欢
    • 2015-04-19
    • 2022-08-17
    • 2016-10-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-03
    • 1970-01-01
    相关资源
    最近更新 更多