威尼斯人线上娱乐

NET基础之自定义泛型,泛型的有余行使

31 3月 , 2019  

本篇小说主要介绍泛型的应用。

目的与泛型

威尼斯人线上娱乐,在.NET中泛型使用尤其频繁,在控制台应用程序中,私下认可的引入了System.Collection.Generics名称空间,当中就提供了小编们平日利用的泛型:List<T>和Dictionary<T>,相信用过它们的都知道它们的强劲。还有一种大家常常使用的粗略的泛型:System.Nullable<T>,即可空类型。我们可以:

本篇中收拾了学习泛型时的知识点, 借使有怎么样错误的地点, 请留言

泛型是.NET Framework 2.0
版类库就已经提供的语法,主要用来提升代码的可重用性、类型安全性和频率。

目录

  • 1.对象
    1.1 匿名类与对象
    1.2 静态类成员与伴生对象
  • 2.泛型
    2.1 型变
    2.2 类型投影
    2.3 泛型函数
    2.4 泛型约束

System.Nullable<int> nullableInt;

泛型的定义

泛型代码可依照自定义供给,写出适用于别的类型、灵活且可采纳的函数和连串,制止双重的代码,用一种清晰和架空的思考表达代码的情致

泛型的概念

1.对象

宣称一个可空的int类型,由于C#语法对那一个做了简化平日我们都不那样写,而是那样写:

泛型函数

示例:

/// 交换变量的值
func exchange<T>(_ one: inout T, _ two: inout T) {
    (one, two) = (two, one)
}

var a = 10.0
var b = 20.0

print("a = \(a), b = \(b)")     // a = 10.0, b = 20.0
exchange(&a, &b)               // 交换a和b的值
print("a = \(a), b = \(b)")     // a = 20.0, b = 10.0

var c = "hellow"
var d = "world"

print("a = \(c), b = \(d)")     // a = hellow, b = world
exchange(&c, &d)               // 交换c和d的值
print("a = \(c), b = \(d)")     // a = world, b = hellow
  • 上述代码中, exchange(_:_:)函数正是三个泛型函数

  • <T>中的<T>是三个占位类型, 在概念过程中不显明具体的类型,
    唯有在函数调用时, 依据传入的值的门类, 来测算出T的现实性品种

    • exchange(&a, &b)TDouble类型
    • exchange(&c, &d)TString类型

只顾: 泛型函数在调用的时候, 会依照传入的值揣摸出相应的类别

  • 泛型函数格式:

func 函数名<占位类型列表>(参数列表) {
    // 函数体
}
  • 注意:
    • 参数列表中, 占位类型列表中的占位类型必须在参数列表中使用
    • NET基础之自定义泛型,泛型的有余行使。如果参数列表中, 多个参数都属于同一的占位类型,
      那么那些参数必备传入一致的种类数据, 例如: 全部传来Int,
      String, 或Double
    • 占位类型列表能够有三个占位类型, 使用逗号分开

上边定义了一个普通类和三个泛型类,大家得以肯定看到泛型类和常常类最大的分别就是多了3个<T>。

1.1 匿名类与目的表明式

Java中有匿名类那些概念,指的是在开创类时无需内定类的名字。在Kotlin中也有成效相似的“匿名类”,叫做对象,举个例证:

Java匿名类

public class Login {

    private String userName;

    public Login(String userName) {
        this.userName = userName;
    }

    public void printlnUserName() {
        System.out.println(userName);
    }
}

public class JavaActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        printlnUserName(new Login("Czh") {
            @Override
            public void printlnUserName() {
                super.printlnUserName();
            }
        });
    }

    public void printlnUserName(Login login) {
        login.printlnUserName();
    }
}

Kotlin完成地点的代码,要用关键字object创立二个后续自有个别(或有个别)类型的匿名类的靶子,如下所示:

class KotlinActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        //object是一个对象,该对象继承自上面的Login
        printlnUserName(object : Login("Czh") {
            override fun printlnUserName() {
            }    
        })
    }

    fun printlnUserName(login: Login) {
        login.printlnUserName()
    }
}

对象object还足以兑现接口,如下所示:

//View.OnClickListener是一个interface
button.setOnClickListener(object : View.OnClickListener {
    override fun onClick(v: View?) {
    }
})

对象和类一样,只可以有三个父类,但足以兑现多个接口,八个超类型跟在冒号:后边用逗号,分隔。
一旦只想建立多少个目的,不再三再四任何类,不兑现别的接口,能够如此写:

fun foo(){
    val abc = object {
            var a = 1
            var b = 2
    }
    Toast.makeText(this, "${abc.a}${abc.b}", Toast.LENGTH_SHORT).show()
}

运营代码,查看结果:

威尼斯人线上娱乐 1

请留意,匿名对象能够看作只在本土和民用功效域中评释的档次。假使您选择匿名对象作为国有函数的归来类型大概当做公有属性的连串,那么该函数或品质的实际类型会是匿名对象评释的超类型,尽管您没有申明任张笑飞类型,就会是
Any。在匿名对象中加上的分子将不能够访问。如下所示:

class User {
    // 私有函数,所以其返回类型是匿名对象类型
    private fun getUserName() = object {
        val userName = "Czh"
    }

    // 公有函数,所以其返回类型是 Any
    fun getAge() = object {
        val age = 22
    }

    fun get() {
        getUserName().userName
        //getAge().age //编译错误
    }
}
  • 里头类访问作用域内的变量

就像是 Java
匿名内部类一样,Java能够用final注解变量,使匿名内部类能够利用来源包括它的功效域的变量。如下所示:

final int age = 22;
printlnUserName(new Login() {
    @Override
    public void printlnUserName() {
        //因为age用final声明,所以不能修改
        if (age == 22){
            return;
        }
  }
});

而Kotlin在匿名对象中可以随心所欲拜访或改动变量age,如下所示:

var age = 22
printlnUserName(object : Login() {
    override fun printlnUserName() {
        age = 23
        Toast.makeText(this@MainActivity, "$age", Toast.LENGTH_SHORT).show()
    }
})

运维代码,查看结果:

威尼斯人线上娱乐 2

int? nullableInt

类型参数

  • exchange(_:_:)函数中, 占位类型T正是一个品种参数的事例, 即:
    T是2个体系参数
  • 类型参数钦点并取名贰个占位类型, 并且紧随在函数名背后,
    使用一对尖括号括起来(例如: <T>)
  • 假定三个档次参数被钦命, 就能够如下使用:
    • 用来定义1个函数的参数类型(例如:
      exchange(_:_:)中的one和two的类型)
    • 做为函数的再次回到值
    • 函数主体中的注释类型
  • 类型参数的定义进程中不会表示任何实际的连串, 只是八个占位,
    当函数被调用时, 会依照传入的值的品类, 推测出切实品种,
    例如地点的DoubleStirng替换掉T
  • 参数类型能够同时存在多少个, 并用逗号分开, 例如: <T, U,
    S>为四个类型参数(占位类型), 名称分别为参数类型T, 参数类型U,
    参数类型S
综上有泛型函数格式如下:
func 函数名<类型参数列表>(参数列表) {
   // 函数体
}

据此,那个<T>就标志了,那么些类是泛型类。个中那一个T,也得以写成A,B,C,D或别的字符。

1.2 伴生对象

Java中有静态类成员,而Kotlin中绝非,要贯彻像静态类成员的效用,就要接纳伴生对象。

Java静态成员:

class User {
    static User instance = new User();

    public void printlnUser() {
    }
}
//调用
User.instance.printlnUser()

Kotlin类内部的对象表明能够用 companion 关键字标记:

class User {
    companion object {
        var instance = User()
    }

    fun printlnUser() {
    }
}
//调用
User.instance.printlnUser()

下边重点介绍一下怎么自定义泛型。

泛型类型

  • 泛型函数是在函数名的末尾紧跟着类型参数列表,
    而泛型类型正是在概念的连串的时候, 在类型名背后紧跟类型参数列表
public class Generic
{
    public String Name;
}

public class Generic<T>
{
    public T Name;
}

泛型

 

示例
  • 泛型类:

泛型类: 
class GenericClass<Element> {
    // 集合
    var items = [Element]()
    // 压栈
    func push(_ item: Element) {
        items.append(item)
    }
    // 出栈
    func pop() -> Element? {
        return items.isEmpty ? nil : items.removeLast()
    }
}
  • 泛型结构体

泛型结构体:
struct GenericStruct<Element> {
    // 集合
    var items = [Element]()
    // 压栈
    mutating func push(_ item: Element) {
        items.append(item)
    }
    // 出栈
    mutating func pop() -> Element? {
        return items.isEmpty ? nil : items.removeLast()
    }
}
  • 泛型枚举:

泛型枚举:
enum GenericEnum<Element> {
    case none
    case some(Element)
}
  • 地点的 GenericClass(类), GenericStruct(结构体),
    GenericEnum(枚举)都以泛型类型, 在档次名后紧跟着泛型的档次参数
    <Element>
  • 泛型类型利用的时候, 必要钦命类型参数的具体项目,
    上面以协会体GenericStruct为例:

// 创建GenericStruct类型的机构体变量struct
// 指定类型参数为 Int
var struct = GenericStruct<Int>()
// 使用struct时, push(_:), pop()方法使用 类型参数的地方 都会替换为Int类型
struct.push(1)
struct.push(2)
struct.push(3)
struct.push("4")   // 报错: 因为push(_:)接收的参数类型已经被替换成Int

let result = struct.pop()   // result = 3

泛型,顾名思义,正是泛指的档次。好比男生,女生,黄人,黄人,能够泛称为【人】。

2.1型变

Java泛型

public class Box<T> {
    public T value;

    public Food(T t) {
        value = t;
    }
}

new Box<String>("123");
new Box<Integer>(1);

对应的Kotlin泛型

class Box<T>(t: T) {
    var value = t
}
var box: Box<String> = Box("123")
var box2: Box<Int> = Box(123)

能够见到Java跟Kotlin定义泛型的措施都以基本上的,分歧的是Java中的泛型有通配符,而Kotlin没有。举个例子:

List<String> strings = new ArrayList<String>();
List<Object> objects = strings;//编译错误

Java编写翻译器不认为List<String>是List<Object>的子类,所以编写翻译不通过。那我们换种写法:

List<String> strings = new ArrayList<String>();
List<Object> objects = new ArrayList<Object>();
objects.addAll(strings);//编译通过

缘何调用addAll()方法就能编写翻译通过呢,看一下她的源码:

boolean addAll(Collection<? extends E> c);

Java泛型提供了问号?通配符,上面的<? extends E>代表此措施接受 E
或然 E 的
一些子类型对象的汇合。所以能够由此addAll()方法把List<String>赋值给List<Object>。

Kotlin的泛型没有提供通配符,取而代之的是outin修饰符。先举个例证:

//用out修饰T
class Box<out T> {
}

威尼斯人线上娱乐 3

(米色波浪线标记处为编写翻译错误)

//用in修饰T
class Box<in T> {
}

威尼斯人线上娱乐 4

(浅米灰波浪线标记处为编写翻译错误)

对照上面两段代码能够看来,用out来修饰T,只可以消费T类型,无法回来T类型;
用in来修饰T,只好回去T类型,无法消费T类型。简单的讲正是 in 是主顾, out
是劳动者。

概念泛型类

给泛型类型丰裕分类(extension)
  • 泛型类型丰盛分类时, 定义中不得以追加新的类型参数,
    也不需求写已有品种参数(编写翻译器也分化意写)
  • 不当写法:

错误一: 分类的定义中不可以增加新的类型参数
extension GenericClass<T> { }
错误二: 分类的定义中不需要写已经有的类型参数
extension GenericClass<Element> { }
  • 上边的代码是正确写法, 在分拣中能够使用类型定义时有的品类参数:

extension GenericClass {
    // 使用已有的 类型参数: Element 做为返回值
    func element(at index: Int) -> Element? {
        if index < items.count {
            return items[index]
        }else {
            return nil
        }
    }
}

就算如此在分拣中不能定义新的项目参数,
不过能够在分拣新定义的措施中引入别的的花色参数

extension GenericClass {
   func exchange<T>(one: inout T, two: inout T) {
       (one, two) = (two, one)
   }
}

但项目只好是1个连串。 那么泛型和花色之间是怎么关联吧?

2.2 类型投影

地点说到了outin修饰符,假使我们不要他们来修饰泛型,会见世那种情形:

class Box<T> {
}

威尼斯人线上娱乐 5

编译不通过,因为Array<T>对于类型T是不可变的,所以Box<Any>和Box<String>哪个人也不是哪个人的子类型,所以编写翻译不通过。对于那种景色,大家依旧得以用outin修饰符来化解,但不是用来修饰Box<T>,如下所示:

fun test(strs: Box<Any>) {
    var objects: Box<in String> = strs
    //编译通过
}

fun test2(strs: Box<String>) {
    var objects: Box<out Any> = strs
    //编译通过
}

地点的缓解方式叫做类型投影,Box<out Any>也等于 Java 的 Box<?
extends Object>、Box<in String>相当于 Java 的 博克斯<? super
Object>。

创造泛型类是需求在类定义中用尖括号语法:

泛型约束

  • 上述全部代码中, 不论是泛型函数中的 类型参数T , 如故泛型类型中的
    类型参数Element , 都能够在调用时钦赐任意二个切实的类型做为替换,
    那是因为自身并不曾给这个参数类型累加别的的约束
那正是说什么样是 参数类型加上封锁呢?

就拿我们平时采纳的Dictionary为例,
我们通晓Dictionary的概念中有四个参数类型, 分别为 KeyValue
, 而且在给Dictionary添加元素的时候,
key的值都是唯一的,即Dictionary根据Key的值来判断是修改大概增新币素,
而斯维夫特中的Dictionary是根据Key的哈希值来判定唯一性的,
也等于说DictionaryKey值必须是可哈希的,
所以Dictionary的连串参数Key有贰个羁绊, 那正是 可哈希的值

  • 下面是Dictionary概念的代码部分, 这里过滤里面包车型地铁贯彻部分,
    当中的where会在背后讲解

public struct Dictionary<Key, Value> : Collection, ExpressibleByDictionaryLiteral where Key : Hashable{}

实在非常粗大略,泛型在概念的时候,是泛指类型;在选拔的时候,就需求被钦赐,到底使用哪个项目。

2.3 泛型函数

不仅仅类能够有档次参数。函数也能够有。类型参数要放在函数名称在此以前:

fun <T> singletonList(item: T): List<T> {
    // ……
}

//调用
val l = singletonList<Int>(1)
singletonList(l)

看似于Java的泛型方法:

public <T> T singletonList(T item) {
    // ……
}

//调用
singletonList(1);
class MyGenericClass<T>
{
    ...
}
品类约束语法
  • 在概念三个种类参数时, 在类型参数前边停放贰个类名也许协议名,
    并用冒号分开, 来定义类型参数类型约束,
    他们将改为类型参数列表的一部分
  • 示例:

泛型函数添加类型约束
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
    // 这里是反省函数的函数体部分
}

泛型类型添加类型约束
class GenericClass<T: SomeClass, U: SomeProtocol> {
    // 类的实现部分
}
  • 上面的 泛型函数泛型类型 都分别有多个档次参数 TU,
    T有2个体系约束: 必须是SomeClass类的子类; U有一个门类约束:
    必须信守SomeProtocol协议的品类

即,使用时,就不在是泛指类型,而是一定类型。

2.4 泛型约束

泛型约束能够范围泛型参数允许使用的连串,如下所示:

Kotlin代码

fun <T : Comparable<T>> sort(list: List<T>) {
}

sort(1) //编译错误
sort(listOf(1)) //编译通过

上述代码把泛型参数允许采用的门类限制为 List<T>

Java中也有相近的泛型约束,对应的代码如下:

public static <T extends Comparable> List<T> sort(List<T> list){
}

若果没有点名泛型约束,Kotlin的泛型参数私下认可类型上界是Any,Java的泛型参数暗许类型上界是Object


T能够是私下的标示符,只要听从命名规则即可。

品种约束实践
  • 未来有3个非泛型函数findIndex(ofString:in:),
    该函数的功用是在一个String数组中寻觅给定的String值的目录,
    若找到匹配的String值, 会重返该String值在String数组中的索引,
    不然赶回nil

func findIndex(ofString valueToFind: String, in array: [String]) -> Int? {

    for (index, value) in array.enumerated() {
        if value == valueToFind {
            return index
        }
    }
    return nil
}
  • findIndex(ofString:in:)函数能够用来查找字符串数组中有些字符串的索引值

let strings = ["a", "b", "c", "d", "e"]
if let foundIndex = findIndex(of: "c", in: strings) {
    print("c 的索引值是 \(foundIndex)")
}
// 打印: c 的索引值是 2
  • 我们领会,
    findIndex(ofString:in:)函数近期只能寻找字符串在数组中的索引值,
    用处不是非常的大。但是,
    我们能够用占位类型T替换掉String类型来写出装有同等效果的泛型函数findIndex(of:in:)
  • 上边便是运用占位类型T替换掉String类型的代码:

func findIndex<T>(of valueToFind: T, in array: [T]) -> Int? {
    for (index, value) in array.enumerated() {
        if value == valueToFind {
            return index
        }
    }
    return nil
}
  • 那段代码看上去能够找寻任意的现实性项目在该类型数组中的索引值,
    然则在Xcode中并不可能健康运转

  • 那是因为在斯威夫特中, 想要相比七个值是还是不是等于,
    那么那三个值的类型必须达成了Equatable协议才足以

  • 对此未兑现Equatable说道的类别, 比如大家自定义的类和结构体的实例,
    是不能够向来利用 == 来相比较的, 因为这几个实例并不知道”相等”意味着什么样,
    是一些内容卓殊才相当于, 依然完全相等才算相等,
    Equatable正是用来表达”相等”意味着什么的

  • 因为唯有遵从Equatable协商的品种才能开始展览相等判断,
    所以上述能够被替换到为任意档次的T就不能符合供给,
    所以大家须要给T加上三个门类约束: 想要替换占位类型T的实际品种,
    必须信守Equatable协议

  • 别的服从Equatable共谋的项目都足以在findIndex(of:in:)中健康运转,
    代码如下:

func findIndex<T: Equatable>(of valueToFind: T, in array: [T]) -> Int? {
    for (index, value) in array.enumerated() {
        if value == valueToFind {
            return index
        }
    }
    return nil
}
  • findIndex(of:in:)唯一的系列参数叫做T: Equatable, 即:
    任意符合Equatable商量的门类T

打比方,定义时,定义了1位。但在运用时,必须鲜明钦命,到底是黄人照旧白种人。

总结

本篇文章相比了Java匿名类、静态类与Kotlin对象的写法和二种语言中对泛型的利用。绝对来说,Kotlin依旧在Java的底子上作了部分更上一层楼,扩张了某个语法糖,更灵活也更安全。

参考文献:
Kotlin语言中文站、《Kotlin程序支付入门精要》

引进阅读:
从Java到Kotlin(一)为啥使用Kotlin
从Java到Kotlin(二)基本语法
从Java到Kotlin(三)类和接口
从Java到Kotlin(五)函数与Lambda表达式
从Java到Kotlin(六)扩展与寄托
从Java到Kotlin(七)反射和注释
从Java到Kotlin(八)Kotlin的别样技术
Kotlin学习质地汇聚


越多优异小说请扫描下方二维码关切微信公众号”AndroidCzh“:这里将长时间为您分享原创作品、Android开发经历等!
QQ交流群: 705929135

威尼斯人线上娱乐 6

 

涉嫌类型(泛型协议)

  • 类、结构体和枚举的泛型类型中, 将类型参数列表身处了类型名的末端,
    而在泛型协议中却不能够这么写

  • 泛型协议的写法与泛型类型悬殊, 需求选拔 associatedtype
    关键字来钦定类型参数

  • 泛型协议中的类型参数又被称呼关联类型,
    其象征的骨子里类型在商榷被接纳时才会被钦点

  • 下边一段代码正是至于联类型的情商:

protocol Container {
    associatedtype ItemType
    mutating func append(item: ItemType)
    var count: Int { get }
    subscript(i: Int) -> ItemType { get }
}
  • 协议Container概念了四个其余选取该协议的项目必须提供的作用

    • 务必能够由此append(_:)方法添加四个项目为ItemType的新因素到容器里

    • 非得能够经过count属性获取容器霜月素的多少, 并重返3个Int值

    • 必须能够经过索引值类型为Int的下标检索到容器中的每三个品种为ItemType的元素

  • 说道中无法定义ItemType的具体项目,
    而任何遵循Container协议的项目都无法不内定关联类型 ItemType
    的切切实实品种

  • 下边包车型客车是一个非泛型的IntStack结构体, 采取并符合了Container协议,
    实现了Container合计的是多个须求:

struct IntStack: Container {
    // 集合数组, 用于存放元素
    var items = [Int]()

    // Container协议部分
    typealias ItemType = Int  // 通过关键字 typealias 指定ItemType的类型为Int
    mutating func append(item: Int) {
        self.push(item)
    }
    var count: Int {
        return items.count
    }
    subscript(i: Int) -> Int {
        return items[i]
    }
}
  • IntStack在实现Container的必要时, 钦命了ItemType的花色为Int,
    typealias ItemType = Int,
    从而将Container协商业中学架空的ItemType类型转换为现实的Int类型

  • 出于斯维夫特的类型猜度,
    实际上不用再IntStack中特地的注脚ItemType的花色为Int也足以,
    那是因为IntStack符合Container协商的装有供给,
    并且在章程中也将ItemType写成了Int品类,
    那样Swift就足以推论出ItemType的品种为Int, 事实上,
    在代码中去除typeealias IntType = Int这一行, 一切还能够健康干活

struct IntStack: Container {

    var items = [Int]()

    mutating func append(item: Int) {
        self.push(item)
    }
    var count: Int {
        return items.count
    }
    subscript(i: Int) -> Int {
        return items[i]
    }
}
  • 地点的代码自动测算出 IntType 的项目是 Int

即: 对于泛型协议, 当有品种遵循该协议的时候,
只需求给未规定具体品种的关系类型所参预的具备办法中,
都交由唯一钦赐项目时, 并不须求特意注解该关联类型的注解也能平常运维,
原因便是斯维夫特的机动测算

  • 也得以让泛型Stack结构体遵守Container协议

struct Stack<Element>: Container {
    var items = [Element]()
    // 由于所有需要关联类型的地方都指定了明确类型, 就不需要在特意的声明关联类型具体是什么类型了, 这里自动推断出 ItemType的类型是Stack对象创建时指定的泛型具体类型
    // typealias ItemType = Element
    mutating func append(item: Element) {
        self.items.append(item)
    }
    var count: Int {
        return items.count
    }
    subscript(i: Int) -> Element {
        return items[i]
    }
}

泛型的选用

可以把品种用在类成员的归来类型,方法参数类型等,例如:

通过扩大2个存在的体系来钦点关联类型
  • 对此贰个已经存在的类型, 并且在概念中一贯不遵守泛型协议,
    大家得以在它的extension中遵从要求的泛型协议, 并且在该扩张中
    也得以活动推导ItemType的品类,
    并不必要写typealias ItemType = Element

struct Stack<Element> {
    var items = [Element]()
}
// 通过扩展遵从泛型协议 Container
extension Stack: Container {
    // 这一行可以不写
    // typealias ItemType = Element

    mutating func append(item: Element) {
        self.items.append(item)
    }
    var count: Int {
        return items.count
    }
    subscript(i: Int) -> Element {
        return items[i]
    }
}

泛型类跟普通类的采取方式一样,都亟待实例化对象,再由对象来调用内部的性质或方法。

class MyGenericClass<T1, T2, T3>
{
    private T1 t1Object;

    public MyGenericClass(T1 item)
    {
        t1Object = item;
    }

    public T1 T1Object
    {
        get
        {
            return t1Object;
        }
    }
}

泛型 Where 语句

  • 上边的描述中,
    类型约束让大家能够为泛型函数泛型类型类型参数概念一些强制需要
  • 除了类型约束以外,
    还有一种方法给泛型函数泛型类型类型参数概念约束,
    那正是where子句
  • 现有如下方法:

func findIndex<T: Equatable>(of valueToFind: T, in array: [T]) -> Int? {
    for (index, value) in array.enumerated() {
        if value == valueToFind {
            return index
        }
    }
    return nil
}
  • 该函数便是类型约束中运用的, 获取三个要素,
    在该因素数组中的索引值的函数, 在占位类型T
    使用: EquatableT拓展了种类约束。
  • 今后得以将该函数使用where子句变形为上面代码:

func findIndex<T>(of valueToFind: T, in array: [T]) -> Int? where T : Equatable {
    for (index, value) in array.enumerated() {
        if value == valueToFind {
            return index
        }
    }
    return nil
}
  • 变形后的代码, 将类型约束领到出来, 放在了重临值的末端,
    使用where子句来表明该约束, 那在语法上从未有过其余难题,
    并且变形后的函数如故能够不奇怪使用

  • 本来, 倘诺仅仅是给类型参数添加类型约束,
    仅仅需求首先种办法就足以了。 实际上where子句还有此外三个用法, 即:
    where子句除了给类型参数添加类型约束外,
    还足以给关联类型添加封锁

  • 透过上面包车型地铁代码示例进行教学where子句给关联类型累加封锁的用法

// 容器协议
protocol Container {
    associatedtype ItemType

    mutating func append(item: ItemType)
}

func allItemsMatch<C1: Container, C2: Container>(_ someContainer: C1, _ anotherContainer: C2) -> Bool {
    // 检查两个容器包含相同数量的元素
    if someContainer.count != anotherContainer.count {
        return false
    }
    // 检查每个元素是否相等
    for i in 0..<someContainer.count {
        if someContainer[i] != anotherContainer[i] {
            return false
        }
    }
    return true
}
  • 上述代码中, allItemsMatch(_:_:)函数是1个泛型函数,
    作用是判定多少个都遵守了Container说道的器皿C1,
    C第22中学具有的因素是还是不是在地方和值上完全相等
  • 由于C1和C2是多个不等的占位类型, 所以C1和C2能够是七个不等的项目

// 遵守Container协议的类型Stack1
class Stack1<Element>: Container {
    var items = [ItemType]()

    typealias ItemType = Element

    func append(item: Element) {
        items.append(item)
    }
}

// 遵守Container协议的类型Stack2
class Stack2<Element>: Container {
    var items = [ItemType]()

    typealias ItemType = Element

    func append(item: Element) {
        items.append(item)
    }
}
  • 地点定义了都遵守Container研商的多个品种Stack1Stack2,
    咱们将选用Stack1Stack2的实例对象开始展览比较

  • 根据allItemsMatch(_:_:)的效率可以断定出,
    三个容器Stack1Stack2只有在要素类型(ItemType)类型一致的意况下才能看清成分是不是等于,
    但是其一约束使用类型约束中的方法无法添加, 所以就有了下边包车型地铁写法:

// 这里只考虑定义部分, 不考虑实现部分
func allItemsMatch<C1: Container, C2: Container>(_ someContainer: C1, _ anotherContainer: C2) -> Bool where C1.ItemType == C2.ItemType {//函数体}
  • 那句代码中, 使用了where语句对C1C2的涉嫌类型实行了约束,
    C1C2ItemType

有了那些where子句后,
只有Stack1Stack2中的成分类型必须一律才能选用该函数

  • 本来仅仅有项目相等判断是不够的,
    容器中的成分还非得遵从Equatable才行,
    C1.ItemType == C2.ItemType并且C1.ItemType: Equatable,
    所以allItemsMatch(_:_:)函数的总体代码如下:

func allItemsMatch<C1: Container, C2: Container>(_ someContainer: C1, _ anotherContainer: C2) -> Bool where C1.ItemType == C2.ItemType, C1.ItemType: Equatable {
    // 检查两个容器包含相同数量的元素
    if someContainer.count != anotherContainer.count {
        return false
    }
    // 检查每个元素是否相等
    for i in 0..<someContainer.count {
        if someContainer[i] != anotherContainer[i] {
            return false
        }
    }
    return true
}
  • 在where子句中, 使用逗号分隔多个约束

类型的定义中也足以采纳where子句添加约束, 用法与在泛型函数中千篇一律,
都写在概念的背后, 大括号{}的前头

  • 示例:

class Stack<Element>: Container where Stack.ItemType : Equatable {
    var items = [ItemType]()

    typealias ItemType = Element

    func append(_ item: Element) {
        items.append(item)
    }
}

上边代码实例化了泛型Generic,实例化时,还点名了该泛型Generic的钦点项目为String。

留意固然不可能假定提供了怎么着品种。上边包车型大巴代码不能够实施:

抱有泛型where子句的扩充
  • 您能够使用泛型where子句做为增添的一有个别, 基于在此以前的事例,
    上边包车型地铁以身作则扩张了泛型Stack结构体, 添加1个isTop:方法

extension Stack where Element: Equatable {
    func isTop(_ item: Element) -> Bool {
        return items.last == item
    }
}
  • 以下是 isTop(_:) 方法的调用方式:

if stackOfStrings.isTop("c") {
    print("Top element is c.")
} else {
    print("Top element is something else.")
}
// 打印 "Top element is c."
  • 万一尝试在蕴藏的因素不切合Equatable协议的栈上调用isTop(_:)方法,
    则会接受编写翻译时不当

struct NotEquatable { }
var notEquatableStack = Stack<NotEquatable>()
let notEquatableValue = NotEquatable()
notEquatableStack.push(notEquatableValue)
notEquatableStack.isTop(notEquatableValue)  // 报错
  • 您能够行使where子句扩张二个体协会议, 基于在此之前的实例,
    上面包车型客车实例扩大了Container协议, 添加一个startsWith(:_)方法

extension Container where ItemType: Equatable {
    func startWith(_ item: ItemType) -> Bool {
        return count >= 1 && self[0] == item
    }
}

extension Array: Container{}

let array = ["a", "b", "c"]

if array.startWith("a") {
    print("array 第一个元素是 a")
}else {
    print("array 第一个元素不是 a")
}
// 打印 array 第一个元素是 a
  • 除了给泛型类型泛型协议分拣中加上上述的where子句外,
    仍是能够间接约束ItemType的实际项目:

extension Container where ItemType == Double {
    func average() -> Double {
        var sum = 0.0
        for i in 0..<count {
            sum += self[i]
        }
        return sum / Double(count)
    }
}
print([1260.0, 1200.0, 98.6, 37.0].average())
// 打印 "648.9"
  • 唯有容器中的成分为Double项目时, 才能够调用该措施

于是要给泛型Generic的品质Name赋值,就须求赋值字符串类型的值。

class MyGenericClass<T1, T2, T3>
{
    private T1 t1Object;

    public MyGenericClass()
    {
        t1Object = new T1();
    }
}
持有泛型 Where 子句的涉嫌类型
  • 除外上述使用方式外, where子句还足以一直在泛型协议的概念中,
    直接给泛型协议参数类型增进封锁

protocol Container {
    associatedtype Item
    mutating func append(_ item: Item)
    var count: Int { get }
    subscript(i: Int) -> Item { get }

    associatedtype Iterator: IteratorProtocol where Iterator.Element == Item
    func makeIterator() -> Iterator
}
  • 上述代码Container协议中,
    通过associatedtype Iterator概念了一个品类参数Iterator,
    并使用类型约束, 使Iterator不可能不遵从IteratorProtocol协议,
    又使用where子句,
    约束了Iterator对应IteratorProtocol协议的类型参数的项目必须和associatedtype Item种类一致

  • 2个合计持续了另三个说道, 你通过在钻探注脚的时候, 包蕴泛型Where子句,
    来添加三个约束到被一连协议的关联类型, 例如,
    上面包车型大巴代码注脚了一个ComparableContainer协议,
    他要求拥有的Item必须是Comparable的

protocol ComparableContainer: Container where Item: Comparable {}
public static void Excute()
{
    Generic<String> gs = new Generic<String>();
    gs.Name = "Kiba518";
}

因为我们不知情T1是或不是有国有的默许构造函数。

上边代码定义了二个Int类型的泛型Generic。

 

public static void Excute()
{
    Generic<int> gs = new Generic<int>();
    gs.Name = 518;
}

default关键字

泛型的默许值

设若大家定义了八个泛型的字段,大家想在构造函数中伊始化它,可是大家不晓得它的引用类型或然值类型,那么default就派上用场了:

泛型的暗许值,如上边代码所示。要求选用default(T)来赋值。

public MyGenericClass()
{
    t1Object = default(T1);
}

无论是泛型到底是String,int,bool只怕是三个Class类型,都能够被自动赋值。

若果是值类型就赋值0,引用类型就赋值null。

public static void Excute()
{
    Generic<int> gs = new Generic<int>();
    gs.Name = 518;
    Generic<Task> gsTask = new Generic<Task>();
    gsTask.Name = new Task(()=> {
        Console.WriteLine("Kiba518");
    });
}

public class Generic<T>
{
    public T Name = default(T); 
}

 

泛型的封锁

封锁类型

在泛型类中,有个特别的羁绊可供大家接纳。

在概念泛型的时候大家得以对项目进行约束,通过where关键字贯彻:

当大家不显得的扬言时,这些约束不存在。但当大家展示的证明的时候,那些约束就会执行。

class MyGenericClass<T1> where T : constraint1,constraint
{
    ...
}

上边,大家来探望这么些专门的牢笼。

constraint定义了自律,两个约束用逗号隔绝,要是有八个项目:

public static void Excute()
{ 
    Generic<FanXing> gFanXing = new Generic<FanXing>();
    Generic<Base> gFanXingBase = new Generic<Base>();
    //Generic<string> gs = new Generic<string>(); 这样定义会报错
} 
public class Generic<T> where T : Base
{
    public T Name = default(T); 
} 
public class Base  
{
    public string Name { get; set; }
}
public class FanXing : Base
{
    public new string Name { get; set; }
}
class MyGenericClass<T1, T2> where T1 : constraint1 where T2 : constraint
{
    ...
}

如上边代码所示,【where T : Base】正是其一尤其的封锁。

上边给出一些可用的束缚

当彰显证明那些约束的时候,定义会限制泛型的花色。

                                       
约束                                                                 
说明

什么样是限制泛型的类别呢?

                                where
T:struct                                    
使用结构约束,类型T必须是值类型

相当粗略,泛型T,是泛指某3个类别。我们在概念泛型类时,还需出示的钦定项目,此时大家体现钦命的品类,要受这几个限制。

                                where
T:calss                                      
类约束钦命,类型T必须是援引类型

本条范围便是指【where T : Base】。

                                where
T:interface                                 
钦赐类型T必须完毕是接口大概达成了接口

它的限量是,要求大家钦定的类型T必须是Base,也许该品种继承自Base,如FanXing类。

                                where
T:base-class                              
钦定类型T必须是基类恐怕派生于基类

泛型的函数

                               where
T:new()                                      
钦点类型T必须有贰个暗中认可构造函数

在C#中,泛型不仅能够用于类,还是能够一贯用来函数。

 

实际使用格局如下:

上边结合以上文化给个实例:(PS不要看到代码多 其实非常粗略的
耐心看下来

 public static void Excute()
 {
     GenericFunc gf = new GenericFunc();
     gf.FanXingFunc<FanXing>(new FanXing() { Name="Kiba518"});
 }
 public class GenericFunc
 {
     public void FanXingFunc<T>(T obj)
     {
         Console.WriteLine(obj.GetType());
     }
 }

先定义多少个类Animal、Cow 、Chicken和SuperCow

很简单,调用泛型函数的时候,钦命泛型函数的[点名项目]即可。

#region Animal 虚基类 有一个name属性 Feed方法和一个虚方法MakeANoise
    //虚基类 有一个name属性 Feed方法和一个虚方法MakeANoise
    public abstract class Animal
    {
        protected string name;

        public string Name
        {
            get
            {
                return name;
            }
            set
            {
                name = value;
            }
        }

        public Animal()
        {
            name = "The animal with no name";
        }

        public Animal(string newName)
        {
            name = newName;
        }

        public void Feed()
        {
            Console.WriteLine("{0} has been fed.", name);
        }

        public abstract void MakeANoise();
    }
    #endregion

//Cow Animal的子类,实现虚方法
    public class Cow:Animal
    {
        public Cow(string name) :
            base(name)
        {
        }
        public override void MakeANoise()
        {
            Console.WriteLine("{0} says 'moo!'", name);
        }
    }

//Chicken类,Animal子类
    public class Chicken:Animal
    {
        public Chicken(string name)
            : base(name)
        { }
        public override void MakeANoise()
        {
            Console.WriteLine("{0} says 'cluck'", name);
        }
    }

//Cow的子类,有一个自己的方法Fly
    class SuperCow : Cow
    {
        public SuperCow(string name) : base(name) 
        {
        }

        public void Fly()
        {
            Console.WriteLine("{0} is flying!", name);
        }

        public override void MakeANoise()
        {
            Console.WriteLine("{0} says 'I am supercow!'", name);
        }

    }

而是,那里大家发现2个题材,那便是,在泛型函数里,使用泛型对象的时候,大家发现目的都以object类型的。

类准备好了后来,大家可以开始定义我们的泛型了:

那我们只要想选拔泛型对象里的性质和方法时,要如何是好吧?

//继承了迭代器接口,这样方便使用Foreach 约束它的类型为Animal及其子类
    public class Farm<T>:IEnumerable<T> where T : Animal
    {
        private List<T> animals = new List<T>();

        public List<T> Animals
        {
            get 
            {
                return animals;    
            }
        }
        //迭代器
        public IEnumerator<T> GetEnumerator()
        {
            return animals.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return animals.GetEnumerator();
        }

        //执行所有animal的MakeANoise()
        public void MakeNoises()
        {
            foreach (T animal in animals)
            {
                animal.MakeANoise();
            }
        }
        //执行所有animal的Feed()
        public void FeedTheAnimals()
        {
            foreach (T animal in animals)
            {
                animal.Feed();
            }
        }
        //获得animals中的cow
        public Farm<Cow> GetCows()
        {
            Farm<Cow> cowFarm = new Farm<Cow>();
            foreach (T animal in animals)
            {
                if (animal is Cow)
                {
                    cowFarm.Animals.Add(animal as Cow);
                }
            }
            return cowFarm;
        }
    }

也很简单,反射就足以了。

泛型定义好了,我们用写代码来调用它:

上边大家抬高3个反射函数GetPropertyValue,专门用来赢得属性。

class Program
    {
        static void Main(string[] args)
        {
            Farm<Animal> farm = new Farm<Animal>();
            farm.Animals.Add(new Cow("Jack"));
            farm.Animals.Add(new Chicken("Vera"));
            farm.Animals.Add(new Chicken("Sally"));
            farm.Animals.Add(new SuperCow("Kevin"));
            farm.MakeNoises();

            Farm<Cow> dairyFarm = farm.GetCows();
            dairyFarm.FeedTheAnimals();

            foreach (Cow cow in dairyFarm)
            {
                if (cow is SuperCow)
                {
                    (cow as SuperCow).Fly();
                }
            }
            Console.ReadKey();
        }
    }
public class GenericFunc
{
    public void FanXingFunc<T>(T obj)
    { 
        var name = GetPropertyValue(obj, "Name");
        Console.WriteLine(name); 
    }
    public object GetPropertyValue(object obj, string name)
    {
        object drv1 = obj.GetType().GetProperty(name).GetValue(obj, null);
        return drv1;
    }
}

结果:

出口结果如下:

威尼斯人线上娱乐 7

威尼斯人线上娱乐 8

这么二个泛型就OK了。

诸如此类我们就赢得了我们想要的结果,假若想利用泛型类里的函数,道理也一如既往,只需求用反射来调用即可。

                                                         ——Stay
hungry!Stay foolish!

结语

看来那里,有些同学只怕会以为泛型很复杂,连使用其指标下的属性,都得反射,太烦琐了,还不比不用呢。

有诸如此类想法的同窗,心里讨论就好了,如若对老车手那样说,他必然会内心默默的微笑,然后对您说,你想的正确性。

接下来,你就从未有过然后了。

泛型的运用,开篇已经说了,首要用在增高代码的可重用性、类型安全性和频率上。

如若只是概念叁个类,调用3个属性,那泛型的存在正是鸡肋。

但实质上,大家的序列永远唯有更复杂,更复杂,更复杂。由此泛型才有了用武之地。

C#语法——委托,架构的血流

C#语法——元组类型

C#语法——await与async的正确性打开药情势


注:此作品为原创,欢迎转发,请在小说页面显著地点给出此文链接!
若您觉得那篇文章还不易,请点击下右下角的【推荐】,万分多谢!


相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图