The project aims to provide hundreds of fully-documented, tested, and useful extension methods to existing standard .NET classes. The API documentation is at https://donetex.github.io/api/index.html.
We have been coding with .NET for more than ten years, and we notice many standard classes (like array, list, dictionary, etc.) lack rich methods to facilitate their use and quick development. Although each piece extension method in this library is not much, we believe as a whole they bring great convenience and help enhance producitivity for other .NET programmers.
Nuget: https://www.nuget.org/packages/DoNetExtensions/. In nuget package manager, enter "Install-Package DoNetExtensions" to get the latest version.
Latest Update: Value Swap, Bit Operations, Conversion to Hexical String, Char Extensions
Usage: Currently all methods are under the same namespace as the classes they extend. Therefore just add reference to the extension library (enter "Install-Package DoNetExtensions" in the nuget package manager), import the standard namespaces like "System.Collections" as usual, and then benefit from the added methods.
Each method may have multiple overloads. We are unable to present them one by one here, but these methods are very intuitive and have full XML documentation. We carefully tag AggressiveInlining attribute to "short" extensions to avoid impacting performance.
By category:
b. Array and Collection Extensions
All extensions:
- Consistent Containment Check; 2) Collection to Array Conversion; 3) Consistent Emptiness Check; 4) Convenient IndexOf; 5) Collection to Concatenated String; 6) Basic Array Operations; 7) Value Swap; 8) Bit Operations; 9) Conversion to Hexical String; 10) Char Extensions; 11) SubArray Methods; 12) Sort Enhancement; 13) Mutable Tuples for Data Processing; 14) Dictionary-Based Counting; 15) ForEach Shortcut; 16. String IndexOf Extensions; 17. Multiple Keyword Search
Instead of "a.Contains(b)", we provide an alternative "b.In(a)". If "a" is a collection, the the method checks if "b" is an element in "a"; if "a" is a dictionary, then the method checks if "b" is a key in "a". This "In" method is somewhat "python" style, shorter and more consistent; besides that, it returns false for null reference.
var arr = new[] {1,2,3};
if (1.In(arr)) // equivalent to arr.Contains(1)
Do something...
var dict = new Dictionary<string, int> { { "a", 1 } };
if ("a".In(dict)) // equivalent to dict.ContainsKey("a")
Do something...
arr = null;
1.In(arr); // returns false
1.NotIn(arr); // returns true
dict = null;
"a".In(dict); // returns false
"a".NotIn(dict); // returns true
We also provide "InAny" and "InAll".
1.InAny(new []{1,2,3}, new []{2,3,4}); // returns true
1.InAll(new []{1,2,3}, new []{2,3,4}); // returns false
The same extension is added for string.
'c'.In("string to check"); // returns true
'c'.NotIn("string to check"); // returns false
'c'.InAll("string to check", "another string"); // returns false
'c'.InAny("string to check", "another string"); // returns true
The same extension is added for Python style range checking. The lower bound is included in the range, while the upper bound is excluded.
// ranges (2,5) represents number 2,3,4
1.In(2,5); // returns false
2.In(2,5); // returns true, lower bound is included
5.In(2,5); // returns false, upper bound is excluded
// range (1,5,2) represents number 1,3, the numbers from 1 to 5 with step 2
2 In(1,5,2); // returns false
3.In(1,5,2); // returns true
In: returns true if the element to check is contained in an array/list/collection/string/range, or a key of a dictionary.
NotIn: negation of In.
InAny: returns true if the element to check is contained in any of the provided arrays/lists/collections/strings.
InAll: returns true if the element to check is contained in all of the provided arrays/lists/collections/strings.
var list = new List<int>();
var arr = list.ToArrayOrNull(); // returns a null reference
list.Add(1);
list.Add(2);
arr = list.ToArrayOrNull(); // returns an array [1,2]
arr = list.ToArrayThenClear(); // returns an array [1,2] and clears the list
arr = list.ToArrayOrNull(); // returns a null reference because list has been cleared.
ToArrayOrNull: returns a null reference if the collection is empty (rather than returns an empty array by the build-in ToArray() method), or otherwise works like build-in ToArray() method.
ToArrayThenClear: works like the build-in ToArray() method, but clears the collection after the elements are output to the array.
ToArrayOrNullThenClear: works like the ToArrayOrNull() method, but clears the collection after the elements are output to the array.
All added methods support conversion starting at a specified index.
Although incredibly useful, the emptiness of an array or a collection has to be checked in a clumsy way, even for today after 10 years.
var arr = new int[] {1,2,3};
if (arr != null && arr.Length != 0) // NOTE: the new syntax "arr?.Length != 0" will not do the check as desired!
Do something...
var list = new List<int> {1,2,3};
if (list != null && arr.Count != 0) // NOTE: have to use a different property "Count"
Do something...
var str = "abc";
if (!string.IsNullOrEmpty(str)) // another style of emptiness check, inconsistent with all others
Do something...
Now with the extension, above can be greatly simplified and more readable. The method is added to both arrays and collections.
var arr = new int[] {1,2,3};
if (arr.IsNotNullOrEmpty())
Do something...
var list = new List<int> { 1,2,3};
if (list.IsNotNullOrEmpty())
Do something...
var dict = new Dictionary<string, int> { { "a", 1 } };
if (dict.IsNotNullOrEmpty())
Do something...
var str = "abc";
if (str.IsNotNullOrEmpty())
Do something...
IsNullOrEmpty: Returns true if a collection/string is a null reference or is an empty collection/string.
IsNotNullOrEmpty: the negation of IsNullOrEmpty.
IsEmpty: Returns true if a collection/string is an empty collection/string (throws an NullReferenceException if it is a null reference).
IsNotEmpty: the negation of IsEmpty.
IsEmptyOrBlank: Returns true if a string is an empty string or contains only white-space characters (throws an NullReferenceException if it is a null reference).
IsNotEmptyOrBlank: the negation of IsEmptyOrBlank, for strings only.
IsNullOrEmptyOrBlank: Returns true if a the string is a null reference, or is an empty string, or is a string with only white-sapce characters.
IsNotNullOrEmptyOrBlank: the negation of IsNullOrEmptyOrBlank, for strings only.
Adds extension methods that dummpy the Array.IndexOf and static Array.IndexOf methods. Also adds support for searches of subarrays.
var arr = new [] {1, 2, 3, 4};
arr.IndexOf(2); //returns 1, equivalent to Array.IndexOf(arr, 2)
arr.IndexOf(2, 2); // searches for 2 starting at position 2 of the array, so returns -1
arr.IndexOf(4, 1, 2); // searches for 4 starting at position 1 of the array, and only compares 2 elements afterwards, so returns -1
arr.IndexOf(4, 1, 3); // return 3
arr.IndexOf(new[] {3, 4}); // searches the subarray and returns 2.
IndexOf: Returns the index of the first occurrence of a target element or a target subarray in the current array.
LastIndexOf: Returns the index of the first occurrence of a target element or a target subarray in the current array (subarray search support not added yet).
ToConcatString: Outputs a concatenated string representation for elements in a collection. For each element, their ToString() method is used.
var arr = new int[] {1,2,3};
arr.ToConcatString(','); // returns "1,2,3"
arr.ToConcatString("--"); // returns "1--2--3"
It is not uncommon that we might need to just add/remove one specified item to/from an array, and return a new array with the item added/removed (for example, such addition/removal is rarely used by the client, and it is not desirable to complicate the code design with other data structure like list or linked list).
var arr = new int[] {1,2,3};
var arr2 = arr.Remove(2); // returns a new array instance [1,3]
var arr3 = arr.RemoveAt(2); // returns a new array instance [1,2]
var arr4 = arr.AddFirst(0); // returns a new array instance [0,1,2,3]
var arr5 = arr.AddLast(4); // returns a new array instance [1,2,3,4]
var arr6 = arr.Insert(18, index:2); // returns a new array [1,2,18,3], with 18 inserted at position 2
var arr7 = arr.Insert(arr, index:2); // returns a new array [1,2,1,2,3,3], with "1,2,3" inserted at position 2
var merged = (new[] { arr, arr2, arr3, arr4, arr5, arr6, arr7}).Merge(); // merges all above arrays into on single array.
AddFirst: Returns a new array with one or more elements appended at the beginning of the current array.
AddLast: Returns a new array with one or more elements appended at the end of the current array.
Remove: Returns a new array with the specified element(s) removed.
RemoveAt: Returns a new array with the element at the specified index(es) removed.
Insert: Returns a new array with one or more elements inserted at the specified index.
Merge: Merges a collection of arrays into a single array.
Swapping two values have been an annoying issue that disrupts code readability. The following extensions address this problem. It is implemented by efficient bit operations when possible.
var a = 1;
var b = 2;
a.Swap(ref b);
Console.WriteLine(a); // prints 2
Console.WriteLine(b); // prints 1
var t1 = DateTime.Now;
var t2 = new DateTime(2010, 10, 22);
t1.Swap(ref t2);
Console.WriteLine(t1); // prints "[10/22/2010 12:00:00 AM]"
Console.WriteLine(t2); // prints the recorded time
Due to complier limitation, currently the extension only supports value types or structs. For reference type, you could consider use Swapper.Swap static method.
var a = "123";
var b = "456";
Swapper.Swap(ref a, ref b); // due to compiler limitation, a static method has to be used for reference types
Swap: Swaps the current value of struct types with another value.
We provide convenient extensions to retrieve higer bits or lower bits of a value of type long/ulong, int/uint, short/ushort or byte. Following gives two examples, for full description see the method XML documentation.
int a = 100;
var a_high = a.High(); // gets the higher 16 bits of this 32-bit integer, represented by a 16-bit unsigned integer, which is 0 in this case.
byte b = 100;
var b_low = b.Low(); // gets the lower 4 bits of _b_. The returned value is a byte, and the lower 4 bits of _b_ is positioned at the lower half of the returned byte. For example, the bits of this case is _01100100_, and it returns _00000100_.
var b_high = b.High(); // gets the higher 4 bits of _b_. The returned value is a byte, and the higher 4 bits of _b_ is positioned at the LOWER half of the returned byte. For example, the bits of this case is _01100100_, and it returns _00000110_.
In addition, you can retrieve all bytes of basic values types float/double, long/ulong, int/uint, short/ushort and DateTime.
var t = DateTime.Now;
t.ToBytes(); // gets a byte array representing the DateTime instance t
long a = 100;
var h_high_bytes = a.High().ToBytes(); // combines with High, Low methods to get high/low bytes.
High: Returns the higher-half bits (the left half if you write the value as a 0-1 string) of a supported value.
Low: Returns the lower-half bits (the right half if you write the value as a 0-1 string) of a supported value.
ToBytes: Returns a byte array representing a value of basic value type.
If a hexical string of a value type is needed, than the following comes handy.
var a = 1234;
a.ToHex(); // returns full 32-bit representation "000004D2"
a.ToHex(fullLength:false); // returns "4D2"
ToHex: Returns the hexical representation of the basic value types (byte, int, float, etc.).
Some static methods of Char class is now available as extension methods. Some useful ones include IsWhiteSpace, IsLetter, IsNumber, IsDigit, IsLetterOrDigit, IsUpper, IsLower, IsPunctuation, IsCurrencySymbol, GetNumericValue, ToLower, ToUpper, etc.
We add some new methods for ASCII characters.
IsASCIIUpper: returns true if the character is from A to Z.
IsASCIILower: returns true if the character is from a to z.
IsASCIIDigit: returns true if the character is from 0 to 9.
IsASCIILetterOrDigit: returns true if the character is an ASCII letter or digit (a-z,A-Z,0-9).
IsASCII: returns true if the character is an ASCII character.
IsNegativeSign: returns true if the character represents the numerical negative sign (e.g. '-') under a culture.
var a = ' ';
a.IsWhiteSpace(); // returns true
a.IsASCIIUpper(); // returns false
a.IsASCII(); // returns true
var arr = new[] {1,2,3,4,5};
var subarr1 = arr.SubArray(1,3); // gets {2,3,4}, a subarray starting at position 1, of length 3
var subarr2 = arr.SubFirst(3); // gets {1,2,3}, a subsarray consisting of the first 3 elements
var subarr3 = arr.SubLast(3); // gets {3,4,5}, a subsarray consisting of the last 3 elements
SubArray: gets a subarray starting at a position of a specified length.
SubFirst: gets a subarray consisting of the beginning elements of the current array.
SubLast: gets a subarray consisting of the ending elements of the current array.
The methods makes the experience of frequent array sorting operations much more comfortable. The sorting is in-place. Use classic non-LINQ implementation for efficiency.
var keys = new []{2,3,2,1,2,5,7};
keys.Sort(); // equivalent to Array.Sort(arr), returns {1,2,2,2,3,5,7}
keys.SortDesc(); // sort descendingly, returns {7,5,3,2,2,2,1}
keys = new []{2,3,2,1,2,5,7};
var values = new []{'b','c','b','a','b','e','g'};
keys.SortWithValues(values); // keys become "{1,2,2,2,3,5,7}" and values become "{'a','b','b','b','c','e','g'}"
keys.SortDescWithValues(values); // keys become "{7,5,3,2,2,2,1}" and values become "{'g','e','c','b','b','b','a'}"
The extension method allows you specify a method to convert each array element to another comparable object for comparison. It has the same result as OrderBy().ToArray(), but the sorting is in-place and faster.
var keys = new []{"we","add","sorting","extensions"};
// returns {"add","we","sorting","extensions"}, same result as keys.OrderBy(key=>key[1]).ToArray()
keys.Sort(key=>key[1]);
// returns {"extensions","sorting","we","add"}
keys.SortDesc(key=>key[1]);
//TODO currently does not support specifying the conversion method while sorting with values
We add an efficent method to find the k th element (or the top k elements) in the array, based on ascending order or descending order. The "top k" elements will be moved to the beginning of the array.
var keys = new[] {2,3,2,1,2,0,7,5,4,3};
// returns 1, and "keys" become "{0,1,2,2,3,7,3,4,5,2}" with the smallest 2 elements moved to the beginning of the array
keys.TopK(2);
// returns 5, and "keys" become "{7,5,4,1,0,3,2,2,3,2}" with the largest 2 elements moved to the beginning of the array
keys.TopKDesc(2);
Sort: in-place sort the array ascendingly.
SortDesc: in-place sort the array descendingly.
SortWithValues: in-place sort the key array ascendingly, and in-place adjust the order of the value array accordingly
SortDescWithValues: in-place sort the key array descendingly, and in-place adjust the order of the value array accordingly
TopK: in-place moves the smallest k elements to the beginning of the array.
TopKDesc: in-place moves the largest k elements to the beginning of the array.
TopKWithValues: in-place moves the smallest k elements of the key array to the beginning, and in-place adjust the order of the value array accordingly
TopKDescWithValues: in-place moves the largest k elements of the key array to the beginning, and in-place adjust the order of the value array accordingly
Simple class implementations for mutable tuples. Neither Tuple or ValueTuple in vallia .NET is intended for data processing in data science or machine learning, making C# very hard to use for the cutting-edge development. Although we no longer often code C# for that purpose, occasionally we still use it for data preprocessing, as it is faster than Python for big data. The immutability of C# tuples make it tedious for the job.
It is very unfortunate that even though C# now supports interactive scripting, it still primarily focuses on software engineering. Our objective is make it better for data pre-processing as best as we can.
The Pair and Triple supports implicit conversion to ValueTuple and Tuple objects. The Pair class in addition has implicit conversion to KeyValuePair class, so they can go in any place that supports build-in tuples. They support arithmetic addition and subtraction.
Both Pair and Triple are well-supported by various extension methods.
var dict1 = new Dictionary<string, Pair<int>>();
var dict2 = new Dictionary<string, (int,int)>();
var list1 = new List<Pair<int>>();
var list2 = new List<(int,int)>();
var pair = new Pair<int>(2,3);
var vallinaPair = (2,3);
dict1.Add("test1", pair);
dict1.Add("test2", vallinaPair); // implicit conversion happens
dict2.Add("test1", pair); // implicit conversion happens
dict2.Add("test2", vallinaPair);
dict1.Add("test3", 2, 3); // a new Add overload that supports convenient syntax for Pair
dict2.Add("test3", 2, 3); // a new Add overload that supports convenient syntax for ValueTuple
// The same support for List.
list1.Add(pair);
list1.Add(vallinaPair); // implicit conversion happens
list2.Add(pair); // implicit conversion happens
list2.Add(vallinaPair);
list1.Add(2,3); // a new Add overload that supports convenient syntax for Pair
list2.Add(2,3); // a new Add overload that supports convenient syntax for ValueTuple
Dictionary is the essential class used for data pre-processing in data analytics, data science, or machine learning. The following extensions make it quick for this purpose.
Stat: counting the number of occurrences of keys, able to specify count increment, supports various increment objects whose addition operator is defined.
MergeStat: merges the countings of a sequence of dictionaries.
// Suppose you want to count occurrences of the following keys.
var keys = new[] {"key1", "key2", "key5", "key1", "key3", "key3", "key4", "key5", "key5"};
// Any class that implements IDictionary<string, TValue> is good for this as long as "+" opeartor is defined for TValue.
// Let's first let TValue be int.
// After execution, the counter becomes { "key1":2, "key2":1, "key3":2, "key4":1, "key5":3 }.
var counter = new Dictionary<string, int>();
foreach (var key in keys)
counter.Stat(key); // increase the count for the current key by 1
// We can specify the increment as 2.
// After execution, the counter becomes { "key1":4, "key2":2, "key3":4, "key4":2, "key5":6 }
counter.Clear();
foreach (var key in keys)
counter.Stat(key, 2);
// We can use a tuple for different counts.
// After execution, the counter2 becomes { "key1":(4,5), "key2":(2,3), "key3":(2,2), "key4":(0,1), "key5":(5,6) },
// and coutner3 becomes { "key1":(2,2), "key2":(1,1), "key3":(2,2), "key4":(1,1), "key5":(3,3) }.
var entries = new[] {("key1",(1,2)), ("key2",(2,3)), ("key5",(2,4)),
("key1",(3,3)), ("key3",(1,0)), ("key3",(1,2)),
("key4",(0,1)), ("key5",(2,1)), ("key5",(1,1))}; // every key is associated with a value tuple
var counter2 = new Dictionary<string, Pair<int>>();
var counter3 = new Dictionary<string, Pair<int>>();
foreach (var entry in entries)
{
counter2.Stat(entry.Item1, entry.Item2); // increase the tuple values, using the added mutable tuple class Pair<int>
counter3.Stat(entry.Item1, (1,1)); // another counting that can be merged with counter2 later
}
// Merges counts, returns { "key1":(6,7), "key2":(3,4), "key3":(4,4), "key4":(1,2), "key5":(8,9) }
var merged = (new[] {counter2, counter3}).MergeStat();
Now you can use following code to quickly specify iterations.
5.ForEach(i => Console.WriteLine(i)); // iteration index as input of the delegate, prints out 0,1,2,3,4
(1,5).ForEach(i => Console.WriteLine(i)); // prints out 1,2,3,4
(1,7,2).ForEach(i => Console.WriteLine(i)); // prints out 1,3,5
var arr = new[] {"a","for","each","short", "cut"};
arr.Foreach(item => Console.WriteLine(item.Length)); // prints out 1,3,4,5,3
arr.Foreach((index,item) => Console.WriteLine($"the length of the {index}th string is {item.Length}")); // accepts iteration indexes
arr.Foreach((index,item) =>
{
Console.WriteLine($"the length of the {index}th string is {item.Length}")};
if (item.Length == 3) return false; // returns false to break the iteration
return true;
); // accepts iteration indexes
A corresponding LastIndexOf
is available for all methods of this category. Negative startIndex
is supported.
IndexOf(predicate, startIndex, count): Reports the index of the first character satisfying the specified predicate. You can specify the search starting position, and the number of character positions to examine.
var str = 'abc def ghj';
str.IndexOf(c=>c.IsWhiteSpace()); // returns 3
str.IndexOf(c=>c.IsWhiteSpace(), startIndex:4); // returns 7
str.IndexOf(c=>c.IsWhiteSpace(), startIndex:-6); // returns 7
str.IndexOf(c=>c.IsWhiteSpace(), startIndex:4, count:2); // returns -1
str.LastIndexOf(c=>c.IsWhiteSpace()); // returns 7
IndexOfAny(chars, startIndex, count, out hitIndex): Reports the index of the first occurrence of any character in a specified char array. The index of the matched char in the char array is returned by an out parameter hitIndex.
var str = 'abc def ghj';
str.IndexOfAny(new[] {'d','g','z'}, out int hitIndex); // function returns 4 ('d' matched), and hitIndex returns 0 (the index of 'd' is 0 in the char array).
IndexOfAny(strings, startIndex, count) : StringSearchResult: Reports the index of the first occurrence of any strings in a specified char array. A StringSearchResult
object is returned containing all needed information.
Adds a MultipleStringSearch
class that provides methods for efficient multiple keywords search. The method could be 3 times faster than naive use of IndexOf for 10 keywords.
var msearcher = new MultipleStringSearch(keywords: new[] {"key1", "key2", "key3", "key4", ...}) // you can pass in as many keywords as you like.
var str = ...; // any string to search for the keywords
var allResults = msearcher.FindAll(str, startIndex:0); // returns an array of StringSearchResult objects
var firstOccurrence = msearch.FindFirst(str, startIndex:0); // returns one StringSearchResult object representing the first occurrence of any of the keyword.
var containsAny = msearch.ContainsAny(str, startIndex:0); // checks if str contains any of the keyword