# 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]