c# contravariance / covariance

Personal understanding:

1. Inverter in upward compatible class

2. Covariant out backward compatible classes

In object-oriented programming, especially when using generics, the in and out keywords are used to restrict covariance and contravariance of type parameters sex.

  1. inKeywords (contravariant):

    • inKeyword used to mark contravariance of generic type parameters. Contravariance means that you can use the base class or superclass of the specified type as the parameter type of the method.
    • When a generic class or interface uses the in keyword to qualify type parameters, the generic type can be assigned to a more general type.
    • For example, if you have a write-only repository, you cannot query it for information, you can only add animals to it. In this case, the generic type parameter can be declared as in Animal, indicating that the type parameter can be Animal or any of its base classes.
  2. outKeywords (covariance):

    • outKeyword used to mark covariance of generic type parameters. Covariance means that you can use a subclass or derived class of the specified type as the return type of the method.
    • When a generic class or interface uses the out keyword to qualify type parameters, the generic type can be assigned to a more specific type.
    • For example, if there is a read-only repository, you cannot add animals through it, only query information. In this case, the generic type parameter can be declared as out Animal, indicating that the type parameter can be Animal or any of its derived classes.

By using the in and out keywords, we can restrict the covariance and contravariance of the type in the generic type parameters to ensure that the type safety. The advantage of this is that it allows easier reuse and flexibility of generic types.

Code example:

// 声明一个只读存储库接口,用于查询动物信息
interface ReadOnlyRepository<out T> {
    fun getAll(): List<T>
    fun getById(id: String): T?
}

// 声明一个只写存储库接口,用于添加动物
interface WriteOnlyRepository<in T> {
    fun add(item: T)
}

// Animal类作为基类
open class Animal(val name: String)

// Dog类继承自Animal
class Dog(name: String) : Animal(name)

// Cat类继承自Animal
class Cat(name: String) : Animal(name)

// 只读存储库实现
class ReadOnlyAnimalRepository : ReadOnlyRepository<Animal> {
    private val animals = listOf(Animal("Lion"), Dog("Buddy"), Cat("Whiskers"))

    override fun getAll(): List<Animal> {
        return animals
    }

    override fun getById(id: String): Animal? {
        return animals.find { it.name == id }
    }
}

// 只写存储库实现
class WriteOnlyAnimalRepository : WriteOnlyRepository<Animal> {
    private val animals = mutableListOf<Animal>()

    override fun add(item: Animal) {
        animals.add(item)
    }
}

fun main() {
    val readOnlyRepo: ReadOnlyRepository<Animal> = ReadOnlyAnimalRepository()
    val writeOnlyRepo: WriteOnlyRepository<Dog> = WriteOnlyAnimalRepository()

    val allAnimals = readOnlyRepo.getAll()
    println("All Animals:")
    allAnimals.forEach { animal ->
        println("- ${animal.name}")
    }

    val dog1 = Dog("Max")
    writeOnlyRepo.add(dog1)
    println("\nAdded Dog:")
    val addedDog = readOnlyRepo.getById(dog1.name)
    println("- ${addedDog?.name}")

    val cat1 = Cat("Misty")
    // writeOnlyRepo.add(cat1)  // Compilation Error: Type mismatch. Required: Dog, Found: Cat
}

In the code example above, we defined two repository interfaces: ReadOnlyRepository and WriteOnlyRepository. ReadOnlyRepositoryThe interface declares a return typeout T, indicating that it can only query (read) animal information. WriteOnlyRepositoryThe interface declares the parameter type in T, indicating that it can only add (write) animals.

Then, we created a ReadOnlyAnimalRepository class to implement the ReadOnlyRepository<Animal> interface for querying animal information. Similarly, we also created a WriteOnlyAnimalRepository class to implement the WriteOnlyRepository<Animal> interface for adding animals.

In the main function, we first assign ReadOnlyAnimalRepository to the readOnlyRepo variable, which is legal because < /span> interface. ReadOnlyAnimalRepository implements the ReadOnlyRepository<Animal>

Then, we assign WriteOnlyAnimalRepository to the writeOnlyRepo variable, which is also legal because WriteOnlyAnimalRepository implements a> method to add variable, we can still only use the WriteOnlyRepository<Animal> interface. Note that although we assign it to the writeOnlyRepoadd

Guess you like

Origin blog.csdn.net/dongnihao/article/details/134555134