扩展IEnumerable时为何无法省略泛型函数调用?
嘿,这个问题我之前折腾过好一会儿!先帮你把场景还原清楚,再一步步说清楚原因和解决办法~
先还原你的代码场景
假设你的IMapper接口是这样定义的:
public interface IMapper<TSource, TDestination> { TDestination Map(TSource source); }
你最初写的扩展方法大概是这样(期望能批量转换枚举集合):
// 你最初尝试的扩展方法,调用时无法自动推断泛型 public static IEnumerable<TDestination> MapAll<TSource, TDestination>( this IEnumerable<TSource> source, IMapper<TSource, TDestination> mapper) { return source.Select(item => mapper.Map(item)); }
然后你想这样简洁调用:
var userList = new List<User>(); var userToDtoMapper = new UserToDtoMapper(); // 这个类实现了IMapper<User, UserDto> // 期望的简洁调用,但编译器报错,必须显式指定<User, UserDto> var userDtoList = userList.MapAll(userToDtoMapper);
无奈只能写成这种繁琐的形式:
var userDtoList = userList.MapAll<User, UserDto>(userToDtoMapper);
为什么编译器没法自动推断?
核心原因是C#的泛型类型推断规则限制:
当你调用userList.MapAll(userToDtoMapper)时,编译器可以从userList(IEnumerable<User>)轻松推断出TSource = User,但TDestination的推断卡壳了——
你传入的userToDtoMapper是具体类UserToDtoMapper,虽然它实现了IMapper<User, UserDto>,但编译器在推断泛型参数时,不会自动从类实现的接口里提取泛型参数TDestination。这是因为一个类可能同时实现多个不同的IMapper<,>接口(比如IMapper<User, UserDto>和IMapper<User, AdminDto>),编译器没办法确定你要使用哪一个接口的泛型参数,所以只能放弃自动推断,要求你显式指定。
另外,从参数类型匹配的角度看:扩展方法的第二个参数要求是IMapper<TSource, TDestination>,但此时TDestination还没确定,编译器无法完成“把UserToDtoMapper隐式转换为IMapper<User, ???>”这个操作,自然就没法推断了。
解决办法:让编译器能顺利推断
这里有两种常用的方案,都能让你回到简洁的调用方式:
方案1:把mapper变量声明为接口类型
如果你在声明mapper时,直接用IMapper<TSource, TDestination>作为类型,编译器就能直接从变量类型拿到泛型参数:
var userList = new List<User>(); // 直接声明为接口类型 IMapper<User, UserDto> mapper = new UserToDtoMapper(); // 现在编译器能完美推断,调用很简洁 var userDtoList = userList.MapAll(mapper);
方案2:修改扩展方法,添加泛型约束
给扩展方法增加一个针对mapper的泛型参数,并通过约束关联到IMapper<TSource, TDestination>,这样编译器就能通过mapper的具体类型反向推断出TDestination:
public static IEnumerable<TDestination> MapAll<TSource, TDestination, TMapper>( this IEnumerable<TSource> source, TMapper mapper) // 约束TMapper必须实现对应的IMapper接口 where TMapper : IMapper<TSource, TDestination> { return source.Select(mapper.Map); }
现在你就可以像最开始期望的那样简洁调用了:
var userList = new List<User>(); var userToDtoMapper = new UserToDtoMapper(); var userDtoList = userList.MapAll(userToDtoMapper); // 编译器自动推断所有泛型参数!
这个方案的好处是不用修改变量声明,完全保留代码的简洁性,同时利用泛型约束给编译器足够的信息来完成推断。
内容的提问来源于stack exchange,提问作者Sonic1015

