本篇纯属抬杠之作,之前我们提到了Swift的泛型Protocol使用associatedtype关键字,而不是使用<Type>语法的泛型参数。这其中有什么好处呢?
我就这个问题搜索了一些回答,大体上提到两点:
<Type>语法对Protocol没有意义,Protocol仅需要定义一个抽象的概念,具体的类型应该由实现的Class来明确,比如:
ClassWithInt<Int>: NumberProtocol ClassWithDouble<Double>: NumberProtocol
associatedtype可以用来给Protocol中特定Func添加泛型约束,而不是限定整个Protocol
protocol GeneratorType { associatedtype Element public mutating func next() -> Self.Element? }
听上去还是有一定道理的,然后实践是检验事实的唯一标准。下面我们通过代码实例来和C#进行对比。首先拿出网上多被引用解释上述两个观点的Swift代码:
public protocol Automobile { associatedtype FuelType associatedtype ExhaustType func drive(fuel: FuelType) -> ExhaustType } public protocol Fuel { associatedtype ExhaustType func consume() -> ExhaustType } public protocol Exhaust { init() func emit() } public struct UnleadedGasoline<E: Exhaust>: Fuel { public func consume() -> E { print("...consuming unleaded gas...") return E() } } public struct CleanExhaust: Exhaust { public init() {} public func emit() { print("...this is some clean exhaust...") } } public class Car<F: Fuel,E: Exhaust>: Automobile where F.ExhaustType == E { public func drive(fuel: F) -> E { return fuel.consume() } } public class Car1<F: Fuel>: Automobile { public func drive(fuel: F) -> F.ExhaustType { return fuel.consume() } }
具体的使用情况如下:
var car = Car<UnleadedGasoline<CleanExhaust>, CleanExhaust>() car.drive(fuel: UnleadedGasoline<CleanExhaust>()).emit() var fusion = Car1<UnleadedGasoline<CleanExhaust>>() fusion.drive(fuel: UnleadedGasoline<CleanExhaust>()).emit()
转换成C#代码的话,有两种思路,首先是把泛型参数放到Interface层面:
public interface Automobile<FuelType, ExhaustType> { ExhaustType Drive(FuelType fuel); } public interface Fuel<ExhaustType> { ExhaustType consume(); } public interface Exhaust { void Emit(); } public class UnleadedGasoline<Exhaust> : Fuel<Exhaust> where Exhaust : new() { public Exhaust consume() { Console.WriteLine("...consuming unleaded gas..."); return new Exhaust(); } } public class CleanExhaust : Exhaust { public void Emit() { Console.WriteLine("...this is some clean exhaust..."); } } public class Car : Automobile<UnleadedGasoline<CleanExhaust>, CleanExhaust> { public CleanExhaust Drive(UnleadedGasoline<CleanExhaust> fuel) { return fuel.consume(); } }
还可以模仿Swift对Automobile多做一层继承进行包装:
public interface Car1<T1> : Automobile<UnleadedGasoline<T1>, T1> where T1 : new() { } public class SimpleCar : Car1<CleanExhaust> { public CleanExhaust Drive(UnleadedGasoline<CleanExhaust> fuel) { return fuel.consume(); } }
调用的时候没有什么太大的差别:
var gaso = new UnleadedGasoline<CleanExhaust>(); var car = new Car(); car.Drive(gaso).Emit(); var simpleCar = new SimpleCar(); simpleCar.Drive(gaso).Emit();
和Swift比较不同的是,我们在Interface就代入了泛型参数。但是由于我们不能直接实例化Interface,所以并不能直接使用Automobile来减少一层继承关系。
因为上述提到的使用associatedtype 的第一点理由见仁见智,这里不分高下。
C#还有第二种思路,就是我也把泛型约束下放到Func层级:
public interface Automobile { ExhaustType Drive<FuelType,ExhaustType>(FuelType fuel) where ExhaustType : new(); } public interface Fuel { ExhaustType consume<ExhaustType>() where ExhaustType : new(); } public class UnleadedGasoline : Fuel { public Exhaust consume<Exhaust>() where Exhaust : new() { Console.WriteLine("...consuming unleaded gas..."); return new Exhaust(); } } public class Car2 : Automobile { public CleanExhaust Drive<UnleadedGasoline, CleanExhaust>(UnleadedGasoline fuel) where CleanExhaust : new() { return (fuel as Fuel).consume<CleanExhaust>(); } }
C#的接口并不能定义构造函数。强行模仿起来还真是有点累啊。最终的使用也很简单:
var fuel = new UnleadedGasoline(); var car2 = new Car2(); car2.Drive<UnleadedGasoline,CleanExhaust>(fuel).Emit();
通篇比较下来,应该说Swift通过associatedtype 关键字和<Type>的混用,使得泛型的定义更为复杂也更灵活了。
GitHub:
https://github.com/manupstairs/LearnSwift
https://github.com/manupstairs/LearnDotNetCore