一段时间以来,我一直在尝试使此静态扩展之类的东西起作用:
public static class MixedRepositoryExtensions {
public static Task<TEntity> FindBySelectorAsync<TRepository, TEntity, TSelector>(
this TRepository repository,
TSelector selector)
where TRepository : IReadableRepository<TEntity>, IListableRepository<TEntity>
where TEntity : class, ISearchableEntity<TSelector>
=> repository.Entities.SingleOrDefaultAsync(x => x.Matches(selector));
}
但是,据我了解,C#在设计上并未将通用约束作为其推理过程的一部分,因此在尝试调用它时会导致以下CS0411错误:
不能从用法中推断出方法'MixedRepositoryExtensions.FindBySelectorAsync(TRepository,TSelector)'的类型参数。尝试显式指定类型参数。
示例调用方法(其中ProjectRepository扩展了IReadableRepository
await (new ProjectRepository()).FindBySelectorAsync(0);
我曾经考虑过在所有调用方上明确定义它们,但是这种方法并不理想,因为它会在很多地方使用,并且会使用很多长名称类型。
我还考虑过将两个接口继承为一个接口,例如:
IReadableAndListableRepository<TEntity> :
IReadableRepository<TEntity>,
IListableRepository<TEntity>
但是,由于我不仅仅拥有一个扩展,而且还拥有更多的扩展,所以我发现这会导致接口爆炸(如果是这样的话)。例如,这将是另一个:
IUpdatableAndListableRepository<TEntity :
IUpdatableRepository<TEntity>,
IListableRepository<TEntity>
我从埃里克·利珀特(Eric Lippert)那里发现了一个暗示,即使用F#可能会有所帮助(因为我越来越绝望):
Generics: Why can't the compiler infer the type arguments in this case?
我在F#上玩了一些,但是发现很少有关于将类型约束到多个接口(或与此相关的任何特定接口)的文档,并且无法克服一些错误。这是我尝试过的最后一次尝试。我意识到该方法不会返回相同的值,我现在只是在尝试使约束发挥良好。对不起,如果做得不好,这是我第一次玩F#。
[<Extension>]
type MixedRepositoryExtensions() =
[<Extension>]
static member inline FindBySelectorAsync<'TSelector, 'TEntity when 'TEntity: not struct and 'TEntity:> ISearchableEntity<'TSelector>>(repository: 'TRepository when 'TRepository:> IReadableRepository<'TEntity> and 'TRepository:> IListableRepository<'TEntity>, selector: 'TSelector) = repository;
但是,此实现会导致以下错误,都涉及定义FindBySelectorAsync的行:
FS0331:无法解析此时或附近的通用构造的隐式实例化,因为它可以解析为多个不相关的类型,例如'IListableRepository <'TEntity>'和'IReadableRepository <'TEntity>'。考虑使用类型注释解决歧义
FS0071:将默认类型'IReadableRepository <'TEntity>'应用于类型推断变量时,类型约束不匹配。类型'IReadableRepository <'TEntity>'与类型'IListableRepository <'TEntity>'不兼容,请考虑添加其他类型约束
所以,我想我的问题是:
根据要求,以下是示例中使用的主要接口:
public interface IRepository<TEntity>
where TEntity : class {
}
public interface IReadableRepository<TEntity> :
IRepository<TEntity>
where TEntity : class {
#region Read
Task<TEntity> FindAsync(TEntity entity);
#endregion
}
public interface IListableRepository<TEntity> :
IRepository<TEntity>
where TEntity : class {
#region Read
IQueryable<TEntity> Entities { get; }
#endregion
}
public interface ISearchableEntity<TSelector> {
bool Matches(TSelector selector);
}
非常感谢以下Zoran Horvat。该解决方案基于他的想法,没有它就不可能。我只是出于自己的目的对其进行了抽象,然后将FixTypes方法移至扩展方法中。这是我来到的最终解决方案:
public interface IMixedRepository<TRepository, TEntity>
where TRepository: IRepository<TEntity>
where TEntity : class { }
public static class MixedRepositoryExtensions {
public static TRepository AsMixedRepository<TRepository, TEntity>(
this IMixedRepository<TRepository, TEntity> repository)
where TRepository : IMixedRepository<TRepository, TEntity>, IRepository<TEntity>
where TEntity : class
=> (TRepository)repository;
}
public static Task<TEntity> FindBySelectorAsync<TRepository, TEntity, TSelector>(
this IMixedRepository<TRepository, TEntity> repository,
TSelector selector)
where TRepository :
IMixedRepository<TRepository, TEntity>,
IReadableRepository<TEntity>,
IListableRepository<TEntity>
where TEntity : class, ISearchableEntity<TSelector>
=> repository.AsMixedRepository().Entities.SingleAsync(selector);
public class ProjectRepository :
IMixedRepository<IProjectRepository, Project>,
IReadableRepository<Project>,
IListableRepository<Project>
{ ... }
最后,可以通过以下方式调用扩展方法:
await (new ProjectRepository())
.FindBySelectorAsync(0);
但是,由于该解决方案使用向下转换,因此缺少一些静态类型。如果将混合存储库向下转换为未实现的存储库,则会引发异常。并且由于对循环约束依赖关系的进一步限制,有可能在运行时打破它。有关完全静态类型的版本,请参见下面的Zoran答案。
另一种基于Zoran答案的解决方案,它确实实现了静态类型输入:
public interface IMixedRepository<TRepository, TEntity>
where TRepository: IRepository<TEntity>
where TEntity : class {
TRepository Mixed { get; }
}
public static class MixedRepositoryExtensions {
public static TRepository AsMixedRepository<TRepository, TEntity>(
this IMixedRepository<TRepository, TEntity> repository)
where TRepository : IMixedRepository<TRepository, TEntity>, IRepository<TEntity>
where TEntity : class
=> repository.Mixed;
}
public static Task<TEntity> FindBySelectorAsync<TRepository, TEntity, TSelector>(
this IMixedRepository<TRepository, TEntity> repository,
TSelector selector)
where TRepository :
IMixedRepository<TRepository, TEntity>,
IReadableRepository<TEntity>,
IListableRepository<TEntity>
where TEntity : class, ISearchableEntity<TSelector>
=> repository.AsMixedRepository().Entities.SingleAsync(selector);
public class ProjectRepository :
IMixedRepository<IProjectRepository, Project>,
IReadableRepository<Project>,
IListableRepository<Project>
{
IProjectRepository IMixedRepository<IProjectRepository, Project>.Mixed { get => this; }
...
}
可以用相同的方式调用。唯一的区别是您必须在每个存储库中实现它。.虽然没有那么麻烦。
答案 0 :(得分:1)
我怀疑是因为TEntity
仅间接或可传递地定义而发生问题。对于编译器,弄清楚TEntity
是什么的唯一方法是深入检查TRepository
。但是,C#编译器不会深入检查类型,而只会观察其立即签名。
我相信,通过从方程式中删除TRepository
,您的所有麻烦都会消失:
public static class MixedRepositoryExtensions {
public static Task<TEntity> FindBySelectorAsync<TEntity, TSelector>(
this IReadableAndListableRepository<TEntity> repository,
TSelector selector)
where TEntity : class, ISearchableEntity<TSelector>
=> repository.Entities.SingleOrDefaultAsync(x => x.Matches(selector));
}
将此方法应用于实现存储库接口的具体对象时,将使用其自己的通用类型参数来推断FindBySelectorAsync
方法的签名。
如果问题在于能够以几种非相等的扩展方法为存储库指定约束列表,那么我认为.NET平台是限制,而不是C#本身。由于F#也可以编译为字节码,因此F#中的泛型类型将受到与C#相同的约束。
我找不到动态解决方案,该解决方案可以即时解决所有类型。但是,有一个技巧可以保留完整的静态键入功能,但是需要每个具体的存储库添加一个附加的属性getter。此属性不能继承或作为扩展附加,因为每种具体类型的返回类型都不同。这是演示此想法的代码(该属性简称为FixTypes
):
public class EntityHolder<TTarget, TEntity>
{
public TTarget Target { get; }
public EntityHolder(TTarget target)
{
Target = target;
}
}
public class PersonsRepository
: IRepository<Person>, IReadableRepository<Person>,
IListableRepository<Person>
{
public IQueryable<Person> Entities { get; } = ...
// This is the added property getter
public EntityHolder<PersonsRepository, Person> FixTypes =>
new EntityHolder<PersonsRepository, Person>(this);
}
public static class MixedRepositoryExtensions
{
// Note that method is attached to EntityHolder, not a repository
public static Task<TEntity> FindBySelectorAsync<TRepository, TEntity, TSelector>(
this EntityHolder<TRepository, TEntity> repository, TSelector selector)
where TRepository : IReadableRepository<TEntity>, IListableRepository<TEntity>
where TEntity : class, ISearchableEntity<TSelector>
=> repository.Target.Entities.SingleOrDefaultAsync(x => x.Matches(selector));
// Note that Target must be added before accessing Entities
}
定义了FixTypes
属性getter的存储库可以按常规方式使用,但是扩展方法仅根据其FixTypes
属性的结果来定义:
new PersonsRepository().FixTypes.FindBySelectorAsync(ageSelector);
答案 1 :(得分:0)
此存储库结构是否设计过度?存储库是只读的还是读写的。
public interface IReadOnlyRepository<TEntity>
where TEntity : class
{
Task<TEntity> FindAsync(TEntity entity);
IQueryable<TEntity> Entities { get; }
// etc.
}
// The read-write version inherits from the read-only interface.
public interface IRepository<TEntity> : IReadOnlyRepository<TEntity>
where TEntity : class
{
void Update(TEntity entity);
void Insert(TEntity entity);
// etc.
}
此外,通过将设计更改为
,您可以摆脱TSelector
public interface ISelector<TEntity>
where TEntity : class
{
bool Matches(TEntity entity);
}
现在,只需要一个类型参数
public static class MixedRepositoryExtensions {
public static Task<TEntity> FindBySelectorAsync<TEntity>(
this IReadOnlyRepository<TEntity> repository,
ISelector<TEntity> selector
) where TEntity : class
=> repository.Entities.SingleOrDefaultAsync(x => selector.Matches(x));
}