Chapter 1 - Item 1 : Prefer Implicitly Typed Local Variables

Effective C# (Covers C# 6.0), (includes Content Update Program): 50 Specific Ways to Improve Your C#, 3rd Edition By Bill Wagner 讀後心得
 

使用 var 宣告物件型別,會讓編譯器自動推斷最適合的型別。作者使用"prefer"而非"always";表示各有其適用的情境。以下為"不應該"使用 var 推斷物件型別的情況。

當物件型別為值型別時(int, float, double, decimal, etc.)應該明確的宣告物件型別。

範例程式碼:

var f = GetMagicNumber( );
var total = 100 * f / 6;
Console.WriteLine( $"Declared Type : {total.GetType( ).Name}, Value : {total}" );

Output:
Declared Type : Double, Value : 166.666666666667
Declared Type : Single, Value : 166.6667
Declared Type : Decimal, Value : 166.666666666666666666666666667
Declared Type : Int32, Value : 166
Declared Type : Int64, Value : 166

其中 f 的型別會與 GetMagicNumber 回傳的型別結果一致;這會直接影響 total 的計算精度。若想在編譯階段明確指定 total 的型別,則 f 不應該交由 var 推斷;而是明確指定 f 型別。

除此之外,物件的型別宣告應該盡量使用 var 宣告;讓編譯器推斷最適合且合理的型別。以下為一個向 DB 取得資料的例子。

範例程式碼:

public IEnumerable<string> FindCustomersStartWith1( string start )
{
    IEnumerable<string> q = from c in db.Customers
                            select c.ContactName;
    
    var q2 = q.Where( s => s.StartWith( start ) );
    
    return q2;
} 

此處有一個很大的效能問題,原因在於 q 被明確指定為 IEnumerable<string>;接著在本機端記憶體操作 q.Where 指定給 q2 (IEnumerable<string>),意味著全部客戶的 ContactName 皆從遠端 DB(假設透過網路)暫存進本機記憶體 q。最後回傳的資料 q2 可能只有幾筆,但已經浪費的大量頻寬在取得所有客戶的資訊(c.ContactName)與額外的本機記憶體操作。

改良後程式碼:

public IEnumerable<string> FindCustomersStartWith2( string start )
{
    var q = from c in db.Customers
            select c.ContactName;
    
    var q2 = q.Where( s => s.StartWith( start ) );
    
    return q2;
} 

現在 q 的型別被推斷為 IQueryable<string>,也就是只組合 queryString 而非直接向 DB 取得資料(節省頻寬);接著呼叫 q.Where 做進一步篩選並指定給 q2(IQueryable<string>)。目前為止,皆尚未真正將客戶資料透過網路讀進本機記憶體;直到外部呼叫 ToList( ) 或是 foreach。如此做法不僅節省頻寬,同時也增加了查詢的效率(節省本機記憶體)。

Note:IQueryable<T> 擴充了 IEnumerable<T>,故回傳型別為 IQueryable<T> 是合法的。
結論:
1. 在不明確指定型別會造成語意不清時,不應該使用 var 推斷型別。

2. 除了 1. 的情況,盡可能使用 var 推斷型別;讓編譯器選擇最有效率的方式。

參考資料:
Returning IEnumerable<T> vs. IQueryable<T>
關於 IQueryable<T> 特性的小實驗