C#/F# 中基于约束的类型推断

我一直在尝试让这个像静态扩展这样的东西工作一段时间:


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<Project> 和 IListableRepository<Project> 并且 project 扩展了 ISearchableEntity<int>):


await (new ProjectRepository()).FindBySelectorAsync(0);

我考虑过在所有调用者上显式定义它们,但是,这种方法并不理想,因为它会在很多地方使用并且有很多长名称类型。


我还考虑过将两个接口继承为一个接口,如下所示:


IReadableAndListableRepository<TEntity> : 

    IReadableRepository<TEntity>,

    IListableRepository<TEntity>

但是,由于我将不止使用一个扩展,而不仅仅是使用一个组合,我发现这会导致界面爆炸(如果是这样的话?)。例如,这将是另一个:


IUpdatableAndListableRepository<TEntity :

    IUpdatableRepository<TEntity>,

    IListableRepository<TEntity>

我在这里从 Eric Lippert 那里找到了一个提示,即使用 F# 可能会有所帮助(因为我已经绝望了):


泛型:为什么编译器不能在这种情况下推断类型参数?


我玩了一下 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 的行:

慕虎7371278
浏览 175回答 2
2回答

守候你守候我

我怀疑问题的发生是因为TEntity只能间接定义,或者说是传递定义。对于编译器,弄清楚是什么的唯一方法是深入TEntity检查。TRepository但是,C# 编译器不会深入检查类型,而只会观察它们的直接签名。我相信通过TRepository从等式中移除,你所有的麻烦都会消失:public static class MixedRepositoryExtensions {&nbsp; &nbsp; public static Task<TEntity> FindBySelectorAsync<TEntity, TSelector>(&nbsp; &nbsp; &nbsp; &nbsp; this IReadableAndListableRepository<TEntity> repository,&nbsp; &nbsp; &nbsp; &nbsp; TSelector selector)&nbsp; &nbsp; &nbsp; &nbsp; where TEntity : class, ISearchableEntity<TSelector>&nbsp; &nbsp; &nbsp; &nbsp; => repository.Entities.SingleOrDefaultAsync(x => x.Matches(selector));}当您将此方法应用于实现存储库接口的具体对象时,它自己的通用类型参数将用于推断FindBySelectorAsync方法的签名。如果问题在于能够在几个不相等的扩展方法中为存储库指定约束列表,那么我认为 .NET 平台是限制,而不是 C# 本身。由于 F# 也编译成字节代码,因此 F# 中的泛型类型将受到与 C# 中相同的约束。我找不到动态解决方案,即动态解决所有类型的解决方案。然而,有一种技巧可以保留完整的静态类型功能,但需要每个具体存储库添加一个额外的属性获取器。此属性不能作为扩展继承或附加,因为它在每个具体类型中的返回类型会有所不同。这是演示这个想法的代码(属性简称为FixTypes):public class EntityHolder<TTarget, TEntity>{&nbsp; &nbsp; public TTarget Target { get; }&nbsp; &nbsp; public EntityHolder(TTarget target)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; Target = target;&nbsp; &nbsp; }}public class PersonsRepository&nbsp; &nbsp; : IRepository<Person>, IReadableRepository<Person>,&nbsp; &nbsp; &nbsp; IListableRepository<Person>{&nbsp; &nbsp; public IQueryable<Person> Entities { get; } = ...&nbsp; &nbsp; // This is the added property getter&nbsp; &nbsp; public EntityHolder<PersonsRepository, Person> FixTypes =>&nbsp; &nbsp; &nbsp; &nbsp; new EntityHolder<PersonsRepository, Person>(this);}public static class MixedRepositoryExtensions&nbsp;{&nbsp; &nbsp; // Note that method is attached to EntityHolder, not a repository&nbsp; &nbsp; public static Task<TEntity> FindBySelectorAsync<TRepository, TEntity, TSelector>(&nbsp; &nbsp; &nbsp; &nbsp; this EntityHolder<TRepository, TEntity> repository, TSelector selector)&nbsp; &nbsp; &nbsp; &nbsp; where TRepository : IReadableRepository<TEntity>, IListableRepository<TEntity>&nbsp; &nbsp; &nbsp; &nbsp; where TEntity : class, ISearchableEntity<TSelector>&nbsp; &nbsp; &nbsp; &nbsp; => repository.Target.Entities.SingleOrDefaultAsync(x => x.Matches(selector));&nbsp; &nbsp; &nbsp; &nbsp; // Note that Target must be added before accessing Entities}定义了属性 getter 的存储库FixTypes可以以通常的方式使用,但扩展方法仅在其FixTypes属性的结果上定义:new PersonsRepository().FixTypes.FindBySelectorAsync(ageSelector);

慕森王

这个存储库结构是不是设计过度了?存储库要么是只读的,要么是读写的。public interface IReadOnlyRepository<TEntity>&nbsp; &nbsp; where TEntity : class{&nbsp; &nbsp; Task<TEntity> FindAsync(TEntity entity);&nbsp; &nbsp; IQueryable<TEntity> Entities { get; }&nbsp; &nbsp; // etc.}// The read-write version inherits from the read-only interface.public interface IRepository<TEntity> : IReadOnlyRepository<TEntity>&nbsp; &nbsp; where TEntity : class{&nbsp; &nbsp; void Update(TEntity entity);&nbsp; &nbsp; void Insert(TEntity entity);&nbsp; &nbsp; // etc.}此外,您可以TSelector通过将设计更改为public interface ISelector<TEntity>&nbsp; &nbsp; where TEntity : class{&nbsp; &nbsp; bool Matches(TEntity entity);}现在,只需要一个类型参数public static class MixedRepositoryExtensions {&nbsp; &nbsp; public static Task<TEntity> FindBySelectorAsync<TEntity>(&nbsp; &nbsp; &nbsp; &nbsp; this IReadOnlyRepository<TEntity> repository,&nbsp; &nbsp; &nbsp; &nbsp; ISelector<TEntity> selector&nbsp; &nbsp; ) where TEntity : class&nbsp; &nbsp; &nbsp; &nbsp; => repository.Entities.SingleOrDefaultAsync(x => selector.Matches(x));}
打开App,查看更多内容
随时随地看视频慕课网APP