C# 您见过的扩展方法的最佳或最有趣的用途是什么?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/954198/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
What is the best or most interesting use of Extension Methods you've seen?
提问by Mark Carpenter
I'm starting to really love extension methods... I was wondering if anyone her has stumbled upon one that really blew their mind, or just found clever.
我开始真正喜欢扩展方法……我想知道她是否偶然发现了一种真正让他们大吃一惊的方法,或者只是发现它很聪明。
An example I wrote today:
我今天写的一个例子:
Edited due to other users' comments:
由于其他用户的评论而编辑:
public static IEnumerable<int> To(this int fromNumber, int toNumber) {
while (fromNumber < toNumber) {
yield return fromNumber;
fromNumber++;
}
}
This allows a for loop to be written as a foreach loop:
这允许将 for 循环编写为 foreach 循环:
foreach (int x in 0.To(16)) {
Console.WriteLine(Math.Pow(2, x).ToString());
}
I can't wait to see other examples! Enjoy!
我迫不及待地想看看其他例子!享受!
回答by Robert Harvey
回答by Josh
The full solution is too large to put here, but I wrote a series of extension methods that would allow you to easily convert a DataTable into a CSV.
完整的解决方案太大而无法放在这里,但我编写了一系列扩展方法,可以让您轻松地将 DataTable 转换为 CSV。
public static String ToCSV(this DataTable dataTable)
{
return dataTable.ToCSV(null, COMMA, true);
}
public static String ToCSV(this DataTable dataTable, String qualifier)
{
return dataTable.ToCSV(qualifier, COMMA, true);
}
private static String ToCSV(this DataTable dataTable, String qualifier, String delimiter, Boolean includeColumnNames)
{
if (dataTable == null) return null;
if (qualifier == delimiter)
{
throw new InvalidOperationException(
"The qualifier and the delimiter are identical. This will cause the CSV to have collisions that might result in data being parsed incorrectly by another program.");
}
var sbCSV = new StringBuilder();
var delimiterToUse = delimiter ?? COMMA;
if (includeColumnNames)
sbCSV.AppendLine(dataTable.Columns.GetHeaderLine(qualifier, delimiterToUse));
foreach (DataRow row in dataTable.Rows)
{
sbCSV.AppendLine(row.ToCSVLine(qualifier, delimiterToUse));
}
return sbCSV.Length > 0 ? sbCSV.ToString() : null;
}
private static String ToCSVLine(this DataRow dataRow, String qualifier, String delimiter)
{
var colCount = dataRow.Table.Columns.Count;
var rowValues = new String[colCount];
for (var i = 0; i < colCount; i++)
{
rowValues[i] = dataRow[i].Qualify(qualifier);
}
return String.Join(delimiter, rowValues);
}
private static String GetHeaderLine(this DataColumnCollection columns, String qualifier, String delimiter)
{
var colCount = columns.Count;
var colNames = new String[colCount];
for (var i = 0; i < colCount; i++)
{
colNames[i] = columns[i].ColumnName.Qualify(qualifier);
}
return String.Join(delimiter, colNames);
}
private static String Qualify(this Object target, String qualifier)
{
return qualifier + target + qualifier;
}
At the end of the day, you could call it like this:
归根结底,你可以这样称呼它:
someDataTable.ToCSV(); //Plain old CSV
someDataTable.ToCSV("\""); //Double quote qualifier
someDataTable.ToCSV("\"", "\t"); //Tab delimited
回答by John Farrell
Well this isn't exactly clever but I've modified the ----OrDefault methods so you could specify a default item inline instead of checking for null later in your code:
好吧,这不是很聪明,但我已经修改了 ----OrDefault 方法,因此您可以内联指定默认项目,而不是稍后在代码中检查 null:
public static T SingleOrDefault<T> ( this IEnumerable<T> source,
Func<T, bool> action, T theDefault )
{
T item = source.SingleOrDefault<T>(action);
if (item != null)
return item;
return theDefault;
}
Its incredible simple but really helps clean up those null checks. Best used when your UI is expecting a list of X items, like a tournament system, or game player slots and you want to display "empty seats".
它令人难以置信的简单,但确实有助于清理那些空检查。当您的 UI 需要 X 项列表(例如锦标赛系统或游戏玩家插槽)并且您希望显示“空座位”时,最适合使用。
Usage:
用法:
return jediList.SingleOrDefault(
j => j.LightsaberColor == "Orange",
new Jedi() { LightsaberColor = "Orange", Name = "DarthNobody");
回答by geofftnz
Here's one I hacked together, so feel free to pick holes in it. It takes an (ordered) list of integers and returns a list of strings of contiguous ranges. eg:
这是我一起破解的一个,所以请随意在其中挑出漏洞。它接受一个(有序的)整数列表并返回一个连续范围的字符串列表。例如:
1,2,3,7,10,11,12 --> "1-3","7","10-12"
The function (within a static class):
函数(在静态类中):
public static IEnumerable<string> IntRanges(this IEnumerable<int> numbers)
{
int rangeStart = 0;
int previous = 0;
if (!numbers.Any())
yield break;
rangeStart = previous = numbers.FirstOrDefault();
foreach (int n in numbers.Skip(1))
{
if (n - previous > 1) // sequence break - yield a sequence
{
if (previous > rangeStart)
{
yield return string.Format("{0}-{1}", rangeStart, previous);
}
else
{
yield return rangeStart.ToString();
}
rangeStart = n;
}
previous = n;
}
if (previous > rangeStart)
{
yield return string.Format("{0}-{1}", rangeStart, previous);
}
else
{
yield return rangeStart.ToString();
}
}
Usage example:
用法示例:
this.WeekDescription = string.Join(",", from.WeekPattern.WeekPatternToInts().IntRanges().ToArray());
This code is used to convert data from a DailyWTF-worthy timetabling application. WeekPattern is a bitmask stored in a string "0011011100...". WeekPatternToInts() converts that to an IEnumerable<int>, in this case [3,4,6,7,8], which becomes "3-4,6-8". It provides the user with a compact description of the academic week ranges that a lecture occurs on.
此代码用于转换来自 DailyWTF 值得的时间表应用程序的数据。WeekPattern 是存储在字符串“0011011100...”中的位掩码。WeekPatternToInts() 将其转换为 IEnumerable<int>,在本例中为 [3,4,6,7,8],它变为“3-4,6-8”。它为用户提供了讲座发生的学术周范围的简洁描述。
回答by rmoore
Two that I like to use are the InsertWhere<T
> and RemoveWhere<T
> Extension Methods that I've written. Working with ObservableCollections in WPF and Silverlight I often need to modify ordered lists without recreating them. These methods allow me to insert and remove according to a supplied Func, so .OrderBy() doesn't need to be re-called.
我喜欢使用的两个是我编写的 InsertWhere <T
> 和 RemoveWhere <T
> 扩展方法。在 WPF 和 Silverlight 中使用 ObservableCollections 我经常需要修改有序列表而不重新创建它们。这些方法允许我根据提供的 Func 插入和删除,因此不需要重新调用 .OrderBy()。
/// <summary>
/// Removes all items from the provided <paramref name="list"/> that match the<paramref name="predicate"/> expression.
/// </summary>
/// <typeparam name="T">The class type of the list items.</typeparam>
/// <param name="list">The list to remove items from.</param>
/// <param name="predicate">The predicate expression to test against.</param>
public static void RemoveWhere<T>(this IList<T> list, Func<T, bool> predicate)
{
T[] copy = new T[] { };
Array.Resize(ref copy, list.Count);
list.CopyTo(copy, 0);
for (int i = copy.Length - 1; i >= 0; i--)
{
if (predicate(copy[i]))
{
list.RemoveAt(i);
}
}
}
/// <summary>
/// Inserts an Item into a list at the first place that the <paramref name="predicate"/> expression fails. If it is true in all cases, then the item is appended to the end of the list.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="list"></param>
/// <param name="obj"></param>
/// <param name="predicate">The sepcified function that determines when the <paramref name="obj"/> should be added. </param>
public static void InsertWhere<T>(this IList<T> list, T obj, Func<T, bool> predicate)
{
for (int i = 0; i < list.Count; i++)
{
// When the function first fails it inserts the obj paramiter.
// For example, in a list myList of ordered Int32's {1,2,3,4,5,10,12}
// Calling myList.InsertWhere( 8, x => 8 > x) inserts 8 once the list item becomes greater then or equal to it.
if(!predicate(list[i]))
{
list.Insert(i, obj);
return;
}
}
list.Add(obj);
}
Edit:
Talljoe made some significant improvements to the RemoveWhere/RemoveAll, that I had hastily constructed. With ~3mill items removing every third one the new version takes only ~50 milliseconds (less then 10 if it can call List.RemoveAll !) as opposed to the RemoveWhere 's multiple seconds (I got tired of waiting for it.)
编辑:
Talljoe 对我匆忙构建的 RemoveWhere/RemoveAll 做了一些重大改进。大约 3mill 项目每删除三分之一,新版本只需要大约 50 毫秒(如果它可以调用 List.RemoveAll 则少于 10 毫秒!),而不是 RemoveWhere 的多秒(我已经厌倦了等待它。)
Here is his greatly improved version, thanks again!
这是他大大改进的版本,再次感谢!
public static void RemoveAll<T>(this IList<T> instance, Predicate<T> predicate)
{
if (instance == null)
throw new ArgumentNullException("instance");
if (predicate == null)
throw new ArgumentNullException("predicate");
if (instance is T[])
throw new NotSupportedException();
var list = instance as List<T>;
if (list != null)
{
list.RemoveAll(predicate);
return;
}
int writeIndex = 0;
for (int readIndex = 0; readIndex < instance.Count; readIndex++)
{
var item = instance[readIndex];
if (predicate(item)) continue;
if (readIndex != writeIndex)
{
instance[writeIndex] = item;
}
++writeIndex;
}
if (writeIndex != instance.Count)
{
for (int deleteIndex = instance.Count - 1; deleteIndex >= writeIndex; --deleteIndex)
{
instance.RemoveAt(deleteIndex);
}
}
}
回答by BFree
Here's one I wrote recently at work and blogged about:
这是我最近在工作和博客上写的一篇文章:
http://crazorsharp.blogspot.com/2009/03/cool-ienumberable-extension-method_25.html
http://crazorsharp.blogspot.com/2009/03/cool-ienumberable-extension-method_25.html
It's basically IEnumerable.ToHtmlTable();
它基本上是 IEnumerable.ToHtmlTable();
回答by geofftnz
Extension method on int to decode a bitmask specifying days (with first day of week being Monday in this case) to an enumeration of DayOfWeek enums:
int 上的扩展方法将指定天数(在这种情况下,一周的第一天是星期一)的位掩码解码为 DayOfWeek 枚举的枚举:
public static IEnumerable<DayOfWeek> Days(this int dayMask)
{
if ((dayMask & 1) > 0) yield return DayOfWeek.Monday;
if ((dayMask & 2) > 0) yield return DayOfWeek.Tuesday;
if ((dayMask & 4) > 0) yield return DayOfWeek.Wednesday;
if ((dayMask & 8) > 0) yield return DayOfWeek.Thursday;
if ((dayMask & 16) > 0) yield return DayOfWeek.Friday;
if ((dayMask & 32) > 0) yield return DayOfWeek.Saturday;
if ((dayMask & 64) > 0) yield return DayOfWeek.Sunday;
}
回答by geofftnz
A pair of extension methods to convert base-36 strings(!) to integers:
一对将 base-36 字符串(!)转换为整数的扩展方法:
public static int ToBase10(this string base36)
{
if (string.IsNullOrEmpty(base36))
return 0;
int value = 0;
foreach (var c in base36.Trim())
{
value = value * 36 + c.ToBase10();
}
return value;
}
public static int ToBase10(this char c)
{
if (c >= '0' && c <= '9')
return c - '0';
c = char.ToUpper(c);
if (c >= 'A' && c <= 'Z')
return c - 'A' + 10;
return 0;
}
(Some genius decided that the best way to store numbers in the database was to encode them to strings. Decimals take too much space. Hex is better, but doesnt use the characters G-Z. So obviously you extend base-16 to base-36!)
(一些天才认为在数据库中存储数字的最佳方法是将它们编码为字符串。小数占用太多空间。十六进制更好,但不使用字符 GZ。所以很明显你将 base-16 扩展到 base-36! )
回答by Michael Haren
I have various .Debugify
extension methods that are useful for dumping objects to a log file. For example, here's my Dictionary debugify (I have these for List, Datatable, param array, etc.):
我有各种.Debugify
扩展方法可用于将对象转储到日志文件。例如,这是我的字典调试(我有这些用于列表、数据表、参数数组等):
public static string Debugify<TKey, TValue>(this Dictionary<TKey, TValue> dictionary) {
string Result = "";
if (dictionary.Count > 0) {
StringBuilder ResultBuilder = new StringBuilder();
int Counter = 0;
foreach (KeyValuePair<TKey, TValue> Entry in dictionary) {
Counter++;
ResultBuilder.AppendFormat("{0}: {1}, ", Entry.Key, Entry.Value);
if (Counter % 10 == 0) ResultBuilder.AppendLine();
}
Result = ResultBuilder.ToString();
}
return Result;
}
And here's one for a DbParameterCollection (useful for dumping database calls to the log file):
这是 DbParameterCollection 的一个(用于将数据库调用转储到日志文件):
public static string Debugify(this DbParameterCollection parameters) {
List<string> ParameterValuesList = new List<string>();
foreach (DbParameter Parameter in parameters) {
string ParameterName, ParameterValue;
ParameterName = Parameter.ParameterName;
if (Parameter.Direction == ParameterDirection.ReturnValue)
continue;
if (Parameter.Value == null || Parameter.Value.Equals(DBNull.Value))
ParameterValue = "NULL";
else
{
switch (Parameter.DbType)
{
case DbType.String:
case DbType.Date:
case DbType.DateTime:
case DbType.Guid:
case DbType.Xml:
ParameterValue
= "'" + Parameter
.Value
.ToString()
.Replace(Environment.NewLine, "")
.Left(80, "...") + "'"; // Left... is another nice one
break;
default:
ParameterValue = Parameter.Value.ToString();
break;
}
if (Parameter.Direction != ParameterDirection.Input)
ParameterValue += " " + Parameter.Direction.ToString();
}
ParameterValuesList.Add(string.Format("{0}={1}", ParameterName, ParameterValue));
}
return string.Join(", ", ParameterValuesList.ToArray());
}
Example result:
结果示例:
Log.DebugFormat("EXEC {0} {1}", procName, params.Debugify);
// EXEC spProcedure @intID=5, @nvName='Michael Haren', @intRefID=11 OUTPUT
Note that if you call this afteryour DB calls, you'll get the output parameters filled in, too. I call this on a line that includes the SP name so I can copy/paste the call into SSMS for debugging.
请注意,如果您在数据库调用之后调用它,您也会得到填充的输出参数。我在包含 SP 名称的行上调用它,以便我可以将调用复制/粘贴到 SSMS 中进行调试。
These make my log files pretty and easy to generate without interrupting my code.
这些使我的日志文件生成起来既漂亮又容易,而不会中断我的代码。
回答by okutane
This one creates array with single element added at the very beginning:
这将创建在最开始添加单个元素的数组:
public static T[] Prepend<T>(this T[] array, T item)
{
T[] result = new T[array.Length + 1];
result[0] = item;
Array.Copy(array, 0, result, 1, array.Length);
return result;
}
string[] some = new string[] { "foo", "bar" };
...
some = some.Prepend("baz");
And this one helps me when I need to convert some expression to it's square:
当我需要将一些表达式转换为正方形时,这个可以帮助我:
public static double Sq(this double arg)
{
return arg * arg;
}
(x - x0).Sq() + (y - y0).Sq() + (z - z0).Sq()