What general programming uses a contradiction?

I read the following post about inconsistency and Lasse W. Carlsen:

Understanding covariant and contravariant interfaces in C #

Despite the fact that I understand the concept, I do not understand why this is useful. For example, why does anyone make a read-only list (as in the message: List<Fish> fishes = GetAccessToFishes(); // for some reason, returns List<Animal> )

I also know that the parameters of overriding methods can be contradictory (conceptually. This is not used in C #, Java and C ++, as far as I know). What examples are where it makes sense?

I would appreciate some simple examples in the real world.

+6
source share
3 answers

(I think this question is more about covariance rather than contravariance, since the example given is about covariance.)

List<Fish> fishes = GetAccessToFishes(); // for some reason, returns List<Animal>

Out of context, this is a bit misleading. In your example, the author intended to convey the idea that, technically, if List<Fish> actually referred to List<Animal> , it would be safe to add Fish to it.

But, of course, this will also add Cow to it, which is clearly wrong.

Therefore, the compiler does not allow you to assign a List<Animal> link to a List<Fish> link.

So, when will it be really safe and helpful?

This is a safe destination if the fee cannot be changed. In C #, an IEnumerable<T> can be an unmodifiable collection.

So you can do it safely:

IEnumerable<Animal> animals = GetAccessToFishes(); // for some reason, returns List<Animal>

because there is no way to add non-fish to animals . It has no methods to allow you to do this.

So, when will it be useful?

This is useful when you want to access a common method or property of a collection that can contain elements of one or more types derived from the base class.

For example, you might have a hierarchy representing different categories of stocks for a supermarket.

Let's say that the base class StockItem has a double SalePrice property.

Suppose also that you have a Shopper.Basket() method that returns an IEnumerable<StockItem> that represents the elements that the customer has in his cart. Items in the cart can be of any specific type derived from StockItem .

In this case, you can add prices for all items in the basket (I wrote this entry without using Linq to clearly define what is happening. The real code will use IEnumerable.Sum() , of course):

 IEnumerable<StockItem> itemsInBasket = shopper.Basket; double totalCost = 0.0; foreach (var item in itemsInBasket) totalCost += item.SalePrice; 

contravariance

An example of using contravariance is when you want to apply some action to an element or collection of elements using the base class type, even if you have a derived type.

For example, you might have a method that applied an action to each element in a StockItem sequence as follows:

 void ApplyToStockItems(IEnumerable<StockItem> items, Action<StockItem> action) { foreach (var item in items) action(item); } 

Using the StockItem example, suppose it has a Print() method that you can use to print it before receiving confirmation. You can call using it as follows:

 Action<StockItem> printItem = item => { item.Print(); } ApplyToStockItems(shopper.Basket, printItem); 

In this example, the types of items in the basket can be Fruit , Electronics , Clothing and so on. But since they all come from StockItem , the code works with all of them.

I hope the usefulness of this code is clear! This is very similar to how many methods in Linq work.

+2
source

Covariance is useful for read-only repositories (out); contravariance for write-only repositories (in).

 public interface IReadRepository<out TVehicle> { TVehicle GetItem(Guid id); } public interface IWriteRepository<in TVehicle> { void AddItem(TVehicle vehicle); } 

Thus, an instance of IReadRepository<Car> also an instance of IReadRepository<Vehicle> , because if you take the car out of the repository, it is also a vehicle; however, the IWriteRepository<Vehicle> instance is also an IWriteRepository<Car> instance, because if you can add the vehicle to the repository, you can write the car to the repository.

This explains the rationale for the out and in keywords for covariance and contravariance.

As for why you might want to make this separation (using separate read-only and write interfaces, which are likely to be implemented by the same specific class), this will help you maintain a clean separation between commands (write operations) and queries (read operations), which are likely to have different performance, consistency, and availability requirements, and therefore require different strategies in your code base.

+1
source

If you really do not understand the concept of contravariance, then such problems can (and will) arise.

Let's build the simplest example:

 public class Human { virtual public void DisplayLanguage() { Console.WriteLine("I do speak a language"); } } public class Asian : Human { override public void DisplayLanguage() { Console.WriteLine("I speak chinesse"); } } public class European : Human { override public void DisplayLanguage() { Console.WriteLine("I speak romanian"); } } 

Ok, here is an example of contra variance

  public class test { static void ContraMethod(Human h) { h.DisplayLanguage(); } static void ContraForInterfaces(IEnumerable<Human> humans) { foreach (European euro in humans) { euro.DisplayLanguage(); } } static void Main() { European euro = new European(); test.ContraMethod(euro); List<European> euroList = new List<European>() { new European(), new European(), new European() }; test.ContraForInterfaces(euroList); } } 

Prior to .NET 4.0, the ContraForInterfaces method was not allowed (therefore, they actually committed BUG :)).

Ok, now what is the point of the ContraForInterfaces method? It's simple, once you have made a list of European countries, the objects inside are ALWAYS Europeans, even if you pass them to a method that accepts IEnumerable <>. This way you will always name the correct method for your object. Covariance and ContraVariance are just a parameter of polymorphism (and nothing more), which is now applied to delegates and interfaces. :)

0
source

Source: https://habr.com/ru/post/956347/


All Articles