整理一下自己經常用到的幾個擴展方法,在實際項目中確實好用,節省了不少的工作量。
1 匿名對象轉化
在WinForm中,如果涉及較長時間的操作,我們一般會用一個BackgroundWorker來做封裝長時間的操作,給它傳遞一個類型參數。
var parm = new { UserId = txtUserId.Text, UserText = txtText.Text, TabIndex = tabControl.SelectedIndex, CheckUrl = urls,
SupportFormat = supportFormat, DeleteMHT = chkDelete.Checked, FileFormat=supportFileFormat }; backgroundWorker.RunWorkerAsync(parm);
注意到一點,我這里沒有用一個類型,而是用一個匿名類型,以節省類型的定義。這種場景經常遇到,比如這個后台方法需要傳三個參數,那個后台方法需要五個參數,如果不用我這個方法,那你需要額外的定義二個類型,分別包含三個參數或是五個參數。
再來看DoWork時,如何使用這個匿名方法
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e) { #region Download backgroundWorker.ReportProgress(10, string.Format("Analyst beginning......")); var parm = e.Argument.TolerantCast(new { UserId = string.Empty, UserText = string.Empty, TabIndex = 0,
CheckUrl = new List<string>(), SupportFormat = string.Empty, DeleteMHT = false, FileFormat=string.Empty});
與RunWorkerAsnyc中傳遞的參數一樣,定義一個匿名類型,各屬性放默認值,然后調用TolerantCast方法,即可得到前面傳入的值。
這個方法很大的方便了這種使用情景:方法與方法之間需要傳遞不固定的參數,但是又不願意重新定義一個新的類型。這個方法不來自於CodeProject,我把它的代碼轉在下面,供您參考
/// <summary> /// 轉換匿名類型 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="o"></param> /// <param name="example"></param> /// <returns></returns> public static T TolerantCast<T>(this object o, T example) where T : class { IComparer<string> comparer = StringComparer.CurrentCultureIgnoreCase; //Get constructor with lowest number of parameters and its parameters var constructor = typeof (T).GetConstructors( BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ).OrderBy(c => c.GetParameters().Length).First(); var parameters = constructor.GetParameters(); //Get properties of input object var sourceProperties = new List<PropertyInfo>(o.GetType().GetProperties()); if (parameters.Length > 0) { var values = new object[parameters.Length]; for (int i = 0; i < values.Length; i++) { Type t = parameters[i].ParameterType; //See if the current parameter is found as a property in the input object var source = sourceProperties.Find(delegate(PropertyInfo item) { return comparer.Compare(item.Name, parameters[i].Name) == 0; }); //See if the property is found, is readable, and is not indexed if (source != null && source.CanRead && source.GetIndexParameters().Length == 0) { //See if the types match. if (source.PropertyType == t) { //Get the value from the property in the input object and save it for use //in the constructor call. values[i] = source.GetValue(o, null); continue; } else { //See if the property value from the input object can be converted to //the parameter type try { values[i] = Convert.ChangeType(source.GetValue(o, null), t); continue; } catch { //Impossible. Forget it then. } } } //If something went wrong (i.e. property not found, or property isn't //converted/copied), get a default value. values[i] = t.IsValueType ? Activator.CreateInstance(t) : null; } //Call the constructor with the collected values and return it. return (T) constructor.Invoke(values); } //Call the constructor without parameters and return the it. return (T) constructor.Invoke(null); }
2 集合對象上的擴展方法
先看例子,下面的測試方法
var @enum = new[] {1, 2, 3, 4}.AsEnumerable(); var sum = 0; @enum.ForEach(n => sum += n);
這個擴展方法,可以直接在一個集合上執行一個Lambda表達式,返回結果,相當於有二個參數的Fun<T,T>,來看它的源代碼定義
public static void ForEach<T>(this IEnumerable<T> @enum, Action<T> mapFunction) { foreach (var item in @enum) mapFunction(item); }
3 字符串類型上的擴展方法
這里可以做的擴展方法比較多,一個明顯的例子就是,依據String類型寫的Helper類型方法最多。
來看一個字符串拼湊的例子,平時我們用string.Format這樣的寫法,如果用下面的擴展方法,看起來更直觀一些。
string s = "{0} ought to be enough for {1}."; string param0 = "64K"; string param1 = "everybody"; string expected = "64K ought to be enough for everybody."; Assert.AreEqual(expected, s.FormatWith(param0, param1),
這個擴展方法的定義也簡單,只有一行代碼
/// <summary> /// Formats a string with two literal placeholders. /// </summary> /// <param name="text">The extension text</param> /// <param name="arg0">Argument 0</param> /// <param name="arg1">Argument 1</param> /// <returns>The formatted string</returns> public static string FormatWith(this string text, object arg0, object arg1) { return string.Format(text, arg0, arg1); }
可以考慮把參數延長到任意個,改寫這個擴展方法,也只需要一行代碼皆可
/// <summary> /// Formats a string with two literal placeholders. /// </summary> /// <param name="text">The extension text</param> /// <param name="arg0">Argument 0</param> /// <param name="arg1">Argument 1</param> /// <returns>The formatted string</returns> public static string FormatWith(this string text, params object[] args)) { return string.Format(text, args); }
另一個有用的擴展方法是字符串與枚舉類型之間的轉化,擴展方法定義如下
/// <summary> /// Parses a string into an Enum /// </summary> /// <typeparam name="T">The type of the Enum</typeparam> /// <param name="value">String value to parse</param> /// <returns>The Enum corresponding to the stringExtensions</returns> public static T ToEnum<T>(this string value) { return ToEnum<T>(value, false); }
參考下面的例子,使用這個實用的擴展方法
enum ProductVersion { Standard, Enteprise, Ultimate } [TestMethod] public void StringToEnumTest() { Assert.AreEqual(ProductVersion.Standard, "Standard".ToEnum<ProductVersion>()); }
4 數字類型上的擴展方法
我這里有一個明顯的例子是顯示容量的擴展方法,請看下面的方法組:
/// <summary> /// Kilobytes /// </summary> /// <param name="value"></param> /// <returns></returns> public static int KB(this int value) { return value * 1024; } /// <summary> /// Megabytes /// </summary> /// <param name="value"></param> /// <returns></returns> public static int MB(this int value) { return value.KB() * 1024; } /// <summary> /// Gigabytes /// </summary> /// <param name="value"></param> /// <returns></returns> public static int GB(this int value) { return value.MB() * 1024; } /// <summary> /// Terabytes /// </summary> /// <param name="value"></param> /// <returns></returns> public static long TB(this int value) { return (long)value.GB() * (long)1024; }
用起來就很輕松了,簡單的一行代碼,獲取容量的數字值
var kb = 1.KB(); var mb = 1.MB(); var gb = 1.GB(); var tb = 1.TB();
最后一組擴展方法在計算百分比中經常遇到,比如統計計算,任務完成百分比計算:
#region PercentageOf calculations /// <summary> /// The numbers percentage /// </summary> /// <param name="number">The number.</param> /// <param name="percent">The percent.</param> /// <returns>The result</returns> public static decimal PercentageOf(this int number, int percent) { return (decimal)(number * percent / 100); } /// <summary> /// Percentage of the number. /// </summary> /// <param name="percent">The percent</param> /// <param name="number">The Number</param> /// <returns>The result</returns> public static decimal PercentOf(this int position, int total) { decimal result = 0; if (position > 0 && total > 0) result = (decimal)position / (decimal)total * 100; return result; } public static decimal PercentOf(this int? position, int total) { if (position == null) return 0; decimal result = 0; if (position > 0 && total > 0) result = (decimal)((decimal)position / (decimal)total * 100); return result; } /// <summary> /// The numbers percentage /// </summary> /// <param name="number">The number.</param> /// <param name="percent">The percent.</param> /// <returns>The result</returns> public static decimal PercentageOf(this int number, float percent) { return (decimal)(number * percent / 100); } /// <summary> /// Percentage of the number. /// </summary> /// <param name="percent">The percent</param> /// <param name="number">The Number</param> /// <returns>The result</returns> public static decimal PercentOf(this int position, float total) { decimal result = 0; if (position > 0 && total > 0) result = (decimal)((decimal)position / (decimal)total * 100); return result; } /// <summary> /// The numbers percentage /// </summary> /// <param name="number">The number.</param> /// <param name="percent">The percent.</param> /// <returns>The result</returns> public static decimal PercentageOf(this int number, double percent) { return (decimal)(number * percent / 100); } /// <summary> /// Percentage of the number. /// </summary> /// <param name="percent">The percent</param> /// <param name="number">The Number</param> /// <returns>The result</returns> public static decimal PercentOf(this int position, double total) { decimal result = 0; if (position > 0 && total > 0) result = (decimal)((decimal)position / (decimal)total * 100); return result; } /// <summary> /// The numbers percentage /// </summary> /// <param name="number">The number.</param> /// <param name="percent">The percent.</param> /// <returns>The result</returns> public static decimal PercentageOf(this int number, decimal percent) { return (decimal)(number * percent / 100); } /// <summary> /// Percentage of the number. /// </summary> /// <param name="percent">The percent</param> /// <param name="number">The Number</param> /// <returns>The result</returns> public static decimal PercentOf(this int position, decimal total) { decimal result = 0; if (position > 0 && total > 0) result = (decimal)position / (decimal)total * 100; return result; } /// <summary> /// The numbers percentage /// </summary> /// <param name="number">The number.</param> /// <param name="percent">The percent.</param> /// <returns>The result</returns> public static decimal PercentageOf(this int number, long percent) { return (decimal)(number * percent / 100); } /// <summary> /// Percentage of the number. /// </summary> /// <param name="percent">The percent</param> /// <param name="number">The Number</param> /// <returns>The result</returns> public static decimal PercentOf(this int position, long total) { decimal result = 0; if (position > 0 && total > 0) result = (decimal)position / (decimal)total * 100; return result; } #endregion
來看幾個測試用例,增強對它的直觀感受,因為涉及到的數值類型多一些,所以擴展方法的數量也多。
Assert.AreEqual(33.0M, 100.PercentageOf(33)); Assert.AreEqual(33.0M, 33.PercentOf(100)); Assert.AreEqual(33.0M, 100.PercentageOf((float)33.0F)); Assert.AreEqual(33.0M, 33.PercentOf((float)100.0F)); Assert.AreEqual(33.0M, 100.PercentageOf((double)33.0F)); Assert.AreEqual(33.0M, 33.PercentOf((double)100.0F)); Assert.AreEqual(33.0M, 100.PercentageOf((decimal)33.0M)); Assert.AreEqual(33.0M, 33.PercentOf((decimal)100.0M)); Assert.AreEqual(33.0M, 100.PercentageOf((long)33)); Assert.AreEqual(33.0M, 33.PercentOf((long)100));