如何使用 switch 语句匹配传递的泛型类型和 return object[]
How to use switch statement to match against passed generic type and return object[]
我是一名测试人员,略懂一点编程知识,但我无法让它工作。
我正在为我的集成测试 (BaseIntegrationTest.cs) 创建一个基础 class。它使用泛型来接受特定测试 class 所针对的 DbContext
和 Repository
类型。基础 class 设置数据库上下文和连接并创建适当 repository/controller classes.
的实例
class BaseIntegrationTest <TContext, TRepo> where TContext : DbContext where TRepo : BaseRepository
{
...
}
除了每个测试可能唯一的DbContext
和Repository
类型class,我们还必须确定DatabaseConnectionString和目标存储库构造函数的签名class(因为发送到构造函数的参数数量可能会有所不同)。
我根据传递的泛型类型确定这些值。 TContext
定义了我使用的 DbConnectionString,TRepo
定义了要使用的 Repository 构造函数签名。为了定义这些东西,我添加了一个 private static inner class GenericTypeHelper
。我的 BaseIntegrationTest
class 看起来像这样...
...
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[] { }
};
}
}
}
这个内部 class 给了我编译错误(如评论中所述),我尝试使用 switch 语句将通用 RepoType
与列出的可能存储库 class 进行比较将传递给 BaseIntegrationTest
的类型。有什么方法可以使用干净的开关功能来匹配通用类型吗?
虽然@Matthew Watson 的回答(在@The General 的帮助下)很聪明并且解决了我的编译问题,但它没有 运行 符合预期。 default(RepoType)
returns 基本类型的正确类型的预期默认值,但对于非基本类型,它是 returns null
。这意味着 switch 语句匹配默认值 _ => new object[] { }
,而不管我通常传入哪个 RepoType
。
不过,我确实通过使用 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[] { }
};
按预期编译和 运行s 的解决方案...
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[] { }
};
我是一名测试人员,略懂一点编程知识,但我无法让它工作。
我正在为我的集成测试 (BaseIntegrationTest.cs) 创建一个基础 class。它使用泛型来接受特定测试 class 所针对的 DbContext
和 Repository
类型。基础 class 设置数据库上下文和连接并创建适当 repository/controller classes.
class BaseIntegrationTest <TContext, TRepo> where TContext : DbContext where TRepo : BaseRepository
{
...
}
除了每个测试可能唯一的DbContext
和Repository
类型class,我们还必须确定DatabaseConnectionString和目标存储库构造函数的签名class(因为发送到构造函数的参数数量可能会有所不同)。
我根据传递的泛型类型确定这些值。 TContext
定义了我使用的 DbConnectionString,TRepo
定义了要使用的 Repository 构造函数签名。为了定义这些东西,我添加了一个 private static inner class GenericTypeHelper
。我的 BaseIntegrationTest
class 看起来像这样...
...
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[] { }
};
}
}
}
这个内部 class 给了我编译错误(如评论中所述),我尝试使用 switch 语句将通用 RepoType
与列出的可能存储库 class 进行比较将传递给 BaseIntegrationTest
的类型。有什么方法可以使用干净的开关功能来匹配通用类型吗?
虽然@Matthew Watson 的回答(在@The General 的帮助下)很聪明并且解决了我的编译问题,但它没有 运行 符合预期。 default(RepoType)
returns 基本类型的正确类型的预期默认值,但对于非基本类型,它是 returns null
。这意味着 switch 语句匹配默认值 _ => new object[] { }
,而不管我通常传入哪个 RepoType
。
不过,我确实通过使用 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[] { }
};
按预期编译和 运行s 的解决方案...
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[] { }
};