Effective C# (Covers C# 6.0), (includes Content Update Program): 50 Specific Ways to Improve Your C#, 3rd Edition By Bill Wagner 讀後心得
C# 4.0 以後,泛型有三種修飾型態,分別為:
1. Invariance(不變數):型別不可被替換。
2. Contravariance(反變數):子類別可代表父類別。
3. Covariance(共變數):父類別可代表子類別。
首先來看一個陣列(covariance)的例子。
程式碼:
abstract public class CelestialBody : IComparable<CelestialBody>
{
public double Mass { get; set; }
public string Name { get; set; }
public int CompareTo( CelestialBody other )
{
// elided.
}
}
public class Asteroid : CelestialBody
{
public static void unsafeVariantArray( CelestialBody[ ] baseItems )
{
baseItems[ 0 ] = new Asteroid( ) { Name = "Hygiea", Mass = 8.85e19 };
}
}
public class Moon : CelestialBody
{
}
public class Planet : CelestialBody
{
}
Client
// Initialize Planet array that replace for CelestialBody.
CelestialBody[ ] array = new Planet[ 5 ];
// Throw System.ArrayTypeMismatchException.
Asteroid.unsafeVariantArray( array2 );
// initialize Asterioid array that replace for CelestialBody.
CelestialBody[ ] array2 = new Asterioid[ 5 ];
// Throw System.ArrayTypeMismatchException.
spaceJunk[ 0 ] = new Planet( );
由於共變數的特性,在執行階段會擲出例外(System.ArrayTypeMismatchException)。
為了在設計類別時能夠更明確的指定輸入、輸出泛型類別;C# 4.0 導入了 in, out 泛型修飾字。
1. Invariance 限制了型別需明確相符。
在先前陣列的例子中,由於無法限制初始化型別;造成擲出例外的情況。為解決這個問題,可使用不變數的設計方式。
程式碼:
public class Asteroid : CelestialBody
{
public static void invariantGeneric( IList<CelestialBody> baseItems )
{
baseItems.Add( new Asteroid( )
{ Name = "Hygiea", Mass = 8.85e19 } );
}
}
Client
// Compile Error.
IList<CelestialBody> list = new List<Planet>( );
// Compile OK.
IList<CelestialBody> list2 = new List<CelestialBody>( );
IList<Planet> list3 = new List<Planet>( );
// Compile Error
Asteroid.invariantGeneric( list3 );
2. Covariance 用在輸出型別(out)。
程式碼:
public class Planet : CelestialBody
{
public static void covariantGeneric( IEnumerable<CelestialBody> baseItems )
{
foreach ( var thing in baseItems )
{
Debug.WriteLine( $"{thing.Name} has mass of {thing.Mass} Kg." );
}
}
}
public interface IEnumerable<out T> : IEnumerable
{
new IEnumerable<T> GetEnumerator( );
}
public interface IEnumerator<out T> : IDisposable, IEnumerator
{
new T Current { get; }
// MoveNext( ), Reset( ) inherited from IEnumerator.
}
Client
// Complile OK.
IEnumerable<CelestialBody> enums = new List<Planet>( );
Planet.covariantGeneric(enums );
Note:在列舉器的 foreach loop 中,無法增減集合中的元素(readonly);因此用子類別(Planet)代替父類別(CelestialBody)是可行的。
3. Contravariance 用在輸入型別(in)。
public interface IComparable<in T>
{
int CompareTo( T other );
}
當 CelestialBody 透過比較 Mass 實作了 IComparable<T>,同時也意味著其子類別(Planet, Moon, Asteroid)也可以使用相同的比較方式。也就是父類別可以代替子類別的意思。
結論:
1. 避免使用陣列儲存有繼承關係的類別物件。
2. 透過 in, out 修飾字修飾泛型,可更明確的表達設計原意。
1. 避免使用陣列儲存有繼承關係的類別物件。
2. 透過 in, out 修飾字修飾泛型,可更明確的表達設計原意。