---
# System prepended metadata

title: overload covariant contravariance (2/23)
tags: [Generics, overload, contravariance, covariant, constraints, note]

---

# overload covariant contravariance (2/23)

###### tags: `overload` `covariant` `contravariance` `Generics` `constraints` `note`

## as operator
> The as operator explicitly **converts** the result of an expression to a given reference or nullable value type. If the conversion isn't possible, the as operator returns null. Unlike a cast expression, **the as operator never throws an exception**.\[1]

...用法範例待補

## Operator overloading
> A user-defined type can overload a predefined C# operator. That is, a type can provide the custom implementation of an operation in case one or both of the operands are of that type.\[2]

- 可以自訂義operator，但須定義在Type內。
- Console.Write(obj)對應非string Type obj的運作模式其實是會印出obj.ToString()的結果。
其根本原因為，**所有(?)**Class都是由Object繼承出來的，MSDN對Object的描述如下
> Supports **all classes** in the .NET class hierarchy and provides low-level services to derived classes. This is the ultimate base class of all .NET classes; **it is the root of the type hierarchy**.

而Console.Write()函式之Overloads - Write(Object)如下
```csharp
public static void Write (object? value);
```
> If value is null, nothing is written and no exception is thrown. **Otherwise, the ToString method of value is called to produce its string representation**, and the resulting string is written to the standard output stream.

可看到，write是可接受null的(object? value)，並且若value非null會自動印出ToString()的回傳字串。

## override ToString()?
那現在問題來了，我們能不能用override亂玩ToString()這個Method的Return Type呢?

**答案是不能**

首先MSDN有一段描述如下
> C# 9.0(.NET 5.) support **covariant** return types

所以就算我們override ToString()，ToString()可以回傳的ReturnType必須滿足 
```csharp
ReturnType is String
```
並且因為
> The is operator doesn't consider user-defined conversions.

那定義一個inherit from String 的Class呢?

**答案也是不能**

因為String是sealed class。
> When applied to a class, the sealed modifier prevents other classes from inheriting from it.

所以我們目前玩不壞C#的ToString() XD。


## 何謂covariant？
把class下所有東西，想像成定義這個類別的限制，越多限制會讓這個類別越小，而將適用小類別的東西當成大類別，便稱為covariant。

### 以多邊形舉例
- 多邊形：由N條直線連接成的封閉形狀，有N個角
- 四邊形：由4條直線組成的多邊形
- 矩形：四個角都是直角的四邊形
- 正方形：四個邊等長的矩形

按照上面的定義，把正方形當成多邊形，就是covariance。
但是多邊形不能當成正方形！

### 再以狗來舉例
```csharp
狗 一隻狗 = new 黃金獵犬();
// 黃金獵犬也算是一種狗

// error 
吉娃娃 很吵的一隻狗 = new 狗(); 
// 那隻狗可能不夠吵，不配當吉娃娃，吉娃娃之恥
```
而被宣告出來的大物品也只能使用大容器定義下的功能/資料。
```csharp
狗 不知道哪來的狗 = new 吉娃娃();
// error
不知道哪來的狗.發瘋();
// 就算他是被assign一個吉娃娃，但這個"不知道哪來的狗"是被視為狗
// 而狗並沒有發瘋這個功能，吉娃娃才有
```

### Method中的covariance
另外想像一個情境：
我們有一個Method，可以計算一台車子(Car)的評分。
而在每一台車子下定義該Method完全無法達到Code Reuse。
故我們想到，把計算評分的Method定義在Car的外面，使用Generic Method讓其適用於所有Car。
```csharp
public interface ICar { }
public class RollsRoyce:ICar { }
public static double Rating<T>(T car) where T:ICar
{
    return 0.0; // We can calculate with proterties of ICar
}
```
Rating() 會將`RollsRoyce`以`ICar`的型式讀入，但如同上面物件宣告的限制，在Rating() 中，我們只能使用`ICar`中就定義好的properties與Methods。
```csharp
Console.WriteLine(Rating(new RollsRoyce()));
```


## 何謂contravariance？
若說covariant是
> 將小物品，放到大類別，**當成**一個大物品。

那contravariance為covariant的相反，是不是
> :x:將大物品，放到小類別，**變成**一個小物品。:x:

之所以打叉:x:，是因為**對於物件的指派**，contravariance並不適用！
也非常地合邏輯，舉例來說，吉娃娃有許多的功能是普通的狗所沒有的。
今天有一隻狗，我們不能把它當成外星生物吉娃娃，硬逼他像吉娃娃那樣發瘋！

既然物件的指派不適用，**那Method的指派呢**？
可以！

想像以下情境，

市面上有一項商品(Method)---只適用於吉娃娃的特製狗屋，因為他能安撫吉娃娃的身心，降低吉娃娃發瘋的頻率。
另外也有普通的狗屋，而商品櫃就像以下程式碼
```csharp
delegate void 狗屋(狗 您的愛犬);
delegate void 吉娃娃狗屋(吉娃娃 外星生物);

static void 普通狗屋(狗 您的愛犬) { }
static void 特製狗屋(吉娃娃 外星生物) { }
```

我買了一間這個狗屋，就如同以下程式碼
```csharp
吉娃娃狗屋 家庭救星 = 特製狗屋
```
過幾天我同學也想買，但那間公司缺貨，所以很缺德的只賣給我同學一間普通的狗屋，就會像以下程式碼
```csharp
吉娃娃狗屋 假的家庭救星 = 普通狗屋
```
因為`吉娃娃`是`狗`的subclass，所以普通狗屋也適用於吉娃娃(covariance)，所以`普通狗屋`是可以當成`吉娃娃狗屋`來使用的，這就是contravariance。

## contravariance搭配delegate
delegate常用於串聯Event，正是使用contracariance的好時機！
```csharp
class A { }
class B:A { }
class C:B { }

static void MethodA(A a) { Console.WriteLine("A"); }
static void MethodB(B b) { Console.WriteLine("B"); }
static void MethodC(C c) { Console.WriteLine("C"); }
delegate void DelC(C c); 

static void Main(string[] args)
{
    DelC TestContravariance = (DelC)MethodA + MethodB + MethodC;
    TestContravariance(new C());
}
/* Output:
A
B
C
*/ 
```


## constraints延伸閱讀
延伸上面勞斯萊斯的情境，若我們希望把Rating\<ICar>當作預設Method，利用overloads讓Rating根據不同車種套用不同計算方式呢？

#### 作法一(CS0111)： both Generic Method different constraint
```csharp
public static double Rating<T>(T car) where T:ICar
{
    return 0.0;
}
public static double Rating<T>(T car) where T:RollsRoyce
{
    return 1.0;
}
```
事實上，這種作法會導致錯誤訊息如下
> Type 'Program' already defines a member called 'Rating' with the same parameter types (CS0111)

其原因為，**constraints並不被視為Method Signatures的一種**，MSDN-Methods描述如下
> Methods are declared in a **class, struct, or interface** by **specifying the access** level such as public or private, **optional modifiers** such as abstract or sealed, the **return value**, the **name** of the method, and **any method parameters**. **These parts together are the signature of the method**.\[4]

並且
> A **return type** of a method **is not** part of the signature of the method for the purposes of **method overloading**. However, it is part of the signature of the method when determining the compatibility between a delegate and the method that it points to.

可以看到，Method Signatures並不包含generics constraints，故作法一的兩種函式對C#而言是完全一樣的，才會有CS0111的錯誤訊息。

#### 作法二：一個Generic一個指定DataType
```csharp
public static double Rating<T>(T car) where T:ICar
{
    return 0.0;
}
public static double Rating(RollsRoyce car)
{
    return 1.0;
}
```
很順利的，這種做法可以讓Rating讀入new RollsRoyce()時，優先選擇下面的overload，且也可以指定選擇Generic Method。
```csharp
Console.WriteLine(Rating<ICar>(new RollsRoyce()));
// Output: 0

Console.WriteLine(Rating(new RollsRoyce()));
// Output: 1
```

#### 新問題-base vs. generic？
```csharp
public class RollsRoyce2:RollsRoyce { }

Console.WriteLine(Rating(new RollsRoyce2()));
// Output: 0
```
繼承自RollsRoyce的RollsRoyce2作為parameter時，C#竟然優先選擇了Generic Method去處理RollsRoyce2。

C#關於overload resolution的描述為，會優先選擇**nearest**的overload，而此處直覺是距離RollsRoyce2較近的是RollsRoyce。看來C#所謂的nearest還是要看一下overload resolution，蠻有趣的，但吃飽太閒= =。

## 心得
1. 編譯器有自己的一套決定overload Method哪個最接近的判斷依據，MSDN沒有寫，要去看C# Language Specification。
2. Covariance vs. Contravariance，差別在一個僅適用於儲存，另一個僅適用於被讀入的parameters。
3. 還不懂cast expression的轉換過程是怎麼運作的，但想先跳過不深入下去了= =
4. Delegate除了像是Method interface，還可以發揮類似cast()運算的功能，讓Method pass by value時將各個Method放入Delegate中再相加。

#### 延伸閱讀:[overload resolution](https://docs.microsoft.com/en-us/dotnet/visual-basic/reference/language-specification/overload-resolution)

## References
\[1] (as-operator)[https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/type-testing-and-cast#as-operator] \
\[2] (operator-overloading)[https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/operator-overloading] \
\[3] (method overloading based on generic constraints)[https://stackoverflow.com/questions/12529966/method-overloading-based-on-generic-constraints] \
\[4] (MSDN-Methods)[https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/methods] \
\[5] (Member Overloading)[https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/member-overloading]