├── Utils.Tests ├── Strings │ ├── SampleEnum.cs │ ├── StringHelper_Base64.cs │ ├── StringHelper_Coalesce.cs │ ├── StringExtensions_ValueOrNull.cs │ ├── StringExtensions_Capitalize.cs │ ├── StringExtensions_EndsWithPart.cs │ ├── StringExtensions_StartsWithPart.cs │ ├── StringHelper_Transliterate.cs │ └── StringExtensions_Parse.cs ├── Linq │ ├── SampleListObject.cs │ ├── SampleTreeNode.cs │ ├── EnumerableExtensions_DistinctBy.cs │ ├── EnumerableExtensions_AddRange.cs │ ├── SampleObject.cs │ ├── EnumerableExtensions_JoinString.cs │ ├── EnumerableExtensions_Random.cs │ ├── ExprHelper_And.cs │ ├── EnumerableExtensions_Recursive.cs │ ├── ExprHelper_Or.cs │ ├── EnumerableExtensions_OrderBy_IEnumerable.cs │ ├── ExprHelper_Apply.cs │ ├── EnumerableExtensions_Partition.cs │ ├── EnumerableExtensions_FirstN.cs │ ├── EnumerableExtensions_OrderBy_IQueryable.cs │ └── EnumerableExtensions_FirstNOrDefault.cs ├── Format │ ├── SampleEnum.cs │ ├── InvariantExtensions_Format.cs │ └── EnumHelper_All.cs ├── RandomTestHelper.cs ├── Utils.Tests.csproj ├── Exceptions │ ├── Try_Do.cs │ └── Try_Get.cs ├── Xml │ ├── XmlHelper_Deserialize.cs │ ├── XmlExtensions_All.cs │ └── XmlHelper_Serialize.cs ├── Comparisons │ ├── CompareHelper_Max.cs │ └── CompareHelper_Min.cs ├── Tasks │ ├── Locker_AcquireAsync.cs │ └── TaskHelper_GetAll.cs ├── Dictionary │ ├── DictionaryHelper_TryGetNullableValue.cs │ └── DictionaryHelper_TryGetValue.cs ├── Url │ ├── UrlHelper_Combine.cs │ └── UrlHelper_GetQuery.cs └── Random │ └── RandomHelper_Values.cs ├── Utils ├── Format │ ├── InvariantExtensions.cs │ └── EnumHelper.cs ├── Url │ ├── UrlHelper.cs │ └── UrlHelper.Query.cs ├── Strings │ ├── StringHelper.cs │ ├── StringExtensions.Case.cs │ ├── StringHelper.Base64.cs │ ├── StringExtensions.Parse.cs │ ├── StringExtensions.cs │ ├── StringHelper.Transliterate.cs │ └── StringHelper.Parse.cs ├── Linq │ ├── EnumerableExtensions.Random.cs │ ├── EnumerableExtensions.Recursive.cs │ ├── EnumerableExtensions.cs │ ├── EnumerableExtensions.Partition.cs │ ├── ExprHelper.AndOr.cs │ ├── EnumerableExtensions.Order.cs │ ├── ExprHelper.Apply.cs │ └── EnumerableExtensions.Destructure.cs ├── Utils.csproj ├── Comparisons │ └── CompareHelper.cs ├── Dictionary │ └── DictionaryExtensions.cs ├── Xml │ ├── XmlExtensions.Attributes.cs │ └── XmlHelper.cs ├── Tasks │ ├── Locker.cs │ └── TaskHelper.cs ├── Exceptions │ └── Try.cs └── Random │ └── RandomHelper.cs ├── LICENSE ├── Utils.sln ├── .gitignore └── README.md /Utils.Tests/Strings/SampleEnum.cs: -------------------------------------------------------------------------------- 1 | namespace Utils.Tests.Strings 2 | { 3 | public enum SampleEnum 4 | { 5 | Foo = 1, 6 | Bar = 2 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Utils.Tests/Linq/SampleListObject.cs: -------------------------------------------------------------------------------- 1 | namespace Utils.Tests.Linq 2 | { 3 | public class SampleListObject 4 | { 5 | public string Key { get; set; } 6 | public string[] Values { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Utils.Tests/Linq/SampleTreeNode.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Utils.Tests.Linq 4 | { 5 | public class SampleTreeNode 6 | { 7 | public SampleTreeNode(int value, params SampleTreeNode[] children) 8 | { 9 | Value = value; 10 | Children = children; 11 | } 12 | 13 | public int Value { get; } 14 | 15 | public IReadOnlyList Children { get; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Utils.Tests/Format/SampleEnum.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace Utils.Tests.Format 4 | { 5 | /// 6 | /// Sample enum. 7 | /// 8 | public enum SampleEnum 9 | { 10 | [Description("First value")] 11 | Hello = 13, 12 | 13 | [Description("Other value")] 14 | World = 37, 15 | } 16 | 17 | /// 18 | /// An enumeration without descriptions. 19 | /// 20 | public enum SampleEnum2 21 | { 22 | Hello = 13, 23 | World = 37 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Utils/Format/InvariantExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Runtime.CompilerServices; 4 | 5 | namespace Impworks.Utils.Format; 6 | 7 | /// 8 | /// Invariant formatting methods. 9 | /// 10 | public static class InvariantExtensions 11 | { 12 | /// 13 | /// Formats the value using Invariant Culture. 14 | /// 15 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 16 | public static string ToInvariantString(this IConvertible value) 17 | { 18 | return value.ToString(CultureInfo.InvariantCulture); 19 | } 20 | } -------------------------------------------------------------------------------- /Utils.Tests/Linq/EnumerableExtensions_DistinctBy.cs: -------------------------------------------------------------------------------- 1 | using Impworks.Utils.Linq; 2 | using NUnit.Framework; 3 | 4 | namespace Utils.Tests.Linq 5 | { 6 | /// 7 | /// Tests for EnumerableExtensions.DistinctBy. 8 | /// 9 | [TestFixture] 10 | public class EnumerableExtensions_DistinctBy 11 | { 12 | #if !NET6_0_OR_GREATER 13 | [Test] 14 | public void DistinctBy_filters_by_predicate() 15 | { 16 | var list = new [] {1, 2, 3, 4, 5}; 17 | var result = list.DistinctBy(x => x % 2); 18 | 19 | Assert.AreEqual(new [] { 1, 2 }, result); 20 | } 21 | #endif 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Utils.Tests/Linq/EnumerableExtensions_AddRange.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Impworks.Utils.Linq; 3 | using NUnit.Framework; 4 | 5 | namespace Utils.Tests.Linq 6 | { 7 | /// 8 | /// Tests for EnumerableExtensions.AddRange. 9 | /// 10 | [TestFixture] 11 | public class EnumerableExtensions_AddRange 12 | { 13 | [Test] 14 | public void AddRange_adds_values() 15 | { 16 | var list = new List {1, 2, 3} as ICollection; 17 | list.AddRange(new [] { 4, 5 }); 18 | 19 | Assert.That(list, Is.EqualTo(new [] { 1, 2, 3, 4, 5 })); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Utils.Tests/Format/InvariantExtensions_Format.cs: -------------------------------------------------------------------------------- 1 | using Impworks.Utils.Format; 2 | using NUnit.Framework; 3 | 4 | namespace Utils.Tests.Format 5 | { 6 | /// 7 | /// InvariantExtensions helpers. 8 | /// 9 | [TestFixture] 10 | public class InvariantExtensions_Format 11 | { 12 | [Test] 13 | public void ToInvariantString_formats_float() 14 | { 15 | Assert.That(1.337f.ToInvariantString(), Is.EqualTo("1.337")); 16 | } 17 | 18 | [Test] 19 | public void ToInvariantString_formats_double() 20 | { 21 | Assert.That(1.337.ToInvariantString(), Is.EqualTo("1.337")); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Utils.Tests/Strings/StringHelper_Base64.cs: -------------------------------------------------------------------------------- 1 | using Impworks.Utils.Strings; 2 | using NUnit.Framework; 3 | 4 | namespace Utils.Tests.Strings 5 | { 6 | [TestFixture] 7 | public class StringHelper_Base64 8 | { 9 | [Test] 10 | public void Encode() 11 | { 12 | var src = "Hello world"; 13 | var encoded = StringHelper.Base64Encode(src); 14 | 15 | Assert.That(encoded, Is.EqualTo("SGVsbG8gd29ybGQ=")); 16 | } 17 | 18 | [Test] 19 | public void Decode() 20 | { 21 | var src = "SGVsbG8gd29ybGQ="; 22 | var encoded = StringHelper.Base64Decode(src); 23 | 24 | Assert.That(encoded, Is.EqualTo("Hello world")); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Utils.Tests/Linq/SampleObject.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Serialization; 2 | 3 | namespace Utils.Tests.Linq 4 | { 5 | /// 6 | /// Object for testing sorting. 7 | /// 8 | public class SampleObject 9 | { 10 | public SampleObject() 11 | { 12 | 13 | } 14 | 15 | public SampleObject(int value, string str) 16 | { 17 | Value = value; 18 | Str = str; 19 | } 20 | 21 | public int Value { get; set; } 22 | 23 | [XmlElement(IsNullable = true)] 24 | public string Str { get; set; } 25 | 26 | public string Field; 27 | 28 | public override string ToString() 29 | { 30 | return $"SampleObj {{ Value = {Value}, Str = {Str}, Field = {Field} }}"; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Utils/Url/UrlHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace Impworks.Utils.Url; 5 | 6 | /// 7 | /// Helper utilities for URLs. 8 | /// 9 | public static partial class UrlHelper 10 | { 11 | /// 12 | /// Safely combines the URI from authority and parts. 13 | /// 14 | public static Uri Combine(string authority, params string[] parts) 15 | { 16 | if (string.IsNullOrEmpty(authority)) 17 | throw new ArgumentNullException(nameof(authority)); 18 | 19 | if (parts == null) 20 | throw new ArgumentNullException(nameof(parts)); 21 | 22 | if (!authority.EndsWith("/")) 23 | authority += "/"; 24 | 25 | var safeParts = parts.Where(x => !string.IsNullOrWhiteSpace(x)) 26 | .Select(x => x.Trim('/', '\\')); 27 | 28 | return new Uri(new Uri(authority), string.Join("/", safeParts)); 29 | } 30 | } -------------------------------------------------------------------------------- /Utils/Strings/StringHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Runtime.CompilerServices; 4 | 5 | namespace Impworks.Utils.Strings; 6 | 7 | /// 8 | /// The collection of various string-related methods. 9 | /// 10 | public static partial class StringHelper 11 | { 12 | /// 13 | /// Returns the first non-empty string from the specified list. 14 | /// 15 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 16 | public static string Coalesce(IEnumerable args) 17 | { 18 | return args.FirstOrDefault(x => !string.IsNullOrEmpty(x)); 19 | } 20 | 21 | /// 22 | /// Returns the first non-empty string from the specified list. 23 | /// 24 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 25 | public static string Coalesce(params string[] args) 26 | { 27 | return Coalesce(args as IEnumerable); 28 | } 29 | } -------------------------------------------------------------------------------- /Utils/Strings/StringExtensions.Case.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Runtime.CompilerServices; 4 | 5 | namespace Impworks.Utils.Strings; 6 | 7 | public static partial class StringExtensions 8 | { 9 | /// 10 | /// Converts the first character of the string to uppercase. 11 | /// 12 | public static string Capitalize(this string str, CultureInfo culture) 13 | { 14 | if (str == null) 15 | throw new ArgumentNullException(nameof(str)); 16 | 17 | if (culture == null) 18 | throw new ArgumentNullException(nameof(culture)); 19 | 20 | if (str.Length == 0) 21 | return str; 22 | 23 | return str.Substring(0, 1).ToUpper(culture) + str.Substring(1); 24 | } 25 | 26 | /// 27 | /// Converts the first character of the string to uppercase. 28 | /// 29 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 30 | public static string Capitalize(this string str) 31 | { 32 | return Capitalize(str, CultureInfo.CurrentCulture); 33 | } 34 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Andrei Kurosh 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Utils.Tests/Linq/EnumerableExtensions_JoinString.cs: -------------------------------------------------------------------------------- 1 | using Impworks.Utils.Linq; 2 | using NUnit.Framework; 3 | 4 | namespace Utils.Tests.Linq 5 | { 6 | /// 7 | /// Tests for EnumerableExtensions.JoinString. 8 | /// 9 | [TestFixture] 10 | public class EnumerableExtensions_JoinString 11 | { 12 | [Test] 13 | public void JoinString_joins_with_separator() 14 | { 15 | Assert.That(new [] { "A", "B", "C" }.JoinString("&"), Is.EqualTo("A&B&C")); 16 | } 17 | 18 | [Test] 19 | public void JoinString_joins_with_empty_separator() 20 | { 21 | Assert.That(new [] { "A", "B", "C" }.JoinString(""), Is.EqualTo("ABC")); 22 | } 23 | 24 | [Test] 25 | public void JoinString_joins_with_null_separator() 26 | { 27 | Assert.That(new [] { "A", "B", "C" }.JoinString(null), Is.EqualTo("ABC")); 28 | } 29 | 30 | [Test] 31 | public void JoinString_returns_empty_string_on_empty_array() 32 | { 33 | Assert.That(new string[0].JoinString(""), Is.EqualTo("")); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Utils.Tests/RandomTestHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NUnit.Framework; 3 | 4 | namespace Utils.Tests 5 | { 6 | /// 7 | /// Testing utilities. 8 | /// 9 | public static class RandomTestHelper 10 | { 11 | private const int ATTEMPTS = 100; 12 | 13 | /// 14 | /// Ensures the function succeeds at least once in a while. 15 | /// 16 | public static void AtLeastOnce(Func attempt) 17 | { 18 | for (var i = 0; i < ATTEMPTS; i++) 19 | { 20 | if (attempt()) 21 | Assert.Pass(); 22 | } 23 | 24 | Assert.Fail($"Failed after {ATTEMPTS} attempts."); 25 | } 26 | 27 | /// 28 | /// Ensures the function succeeds in all attempts. 29 | /// 30 | public static void Always(Func attempt) 31 | { 32 | for (var i = 0; i < ATTEMPTS; i++) 33 | { 34 | if (!attempt()) 35 | Assert.Fail($"Failed on {i} attempt."); 36 | } 37 | 38 | Assert.Pass(); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Utils/Strings/StringHelper.Base64.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | using System.Text; 4 | 5 | namespace Impworks.Utils.Strings; 6 | 7 | public static partial class StringHelper 8 | { 9 | /// 10 | /// Converts a string to base64 encoding. 11 | /// 12 | /// String to encode. 13 | /// Encoding to use. Defaults to UTF-8. 14 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 15 | public static string Base64Encode(string str, Encoding enc = null) 16 | { 17 | var encoding = enc ?? Encoding.UTF8; 18 | return Convert.ToBase64String(encoding.GetBytes(str)); 19 | } 20 | 21 | /// 22 | /// Converts a string from base64 encoding. 23 | /// 24 | /// String to decode. 25 | /// Encoding to use. Defaults to UTF-8. 26 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 27 | public static string Base64Decode(string str, Encoding enc = null) 28 | { 29 | var encoding = enc ?? Encoding.UTF8; 30 | return encoding.GetString(Convert.FromBase64String(str)); 31 | } 32 | } -------------------------------------------------------------------------------- /Utils.Tests/Strings/StringHelper_Coalesce.cs: -------------------------------------------------------------------------------- 1 | using Impworks.Utils.Strings; 2 | using NUnit.Framework; 3 | 4 | namespace Utils.Tests.Strings 5 | { 6 | /// 7 | /// Tests for StringHelper.Coalesce. 8 | /// 9 | [TestFixture] 10 | public class StringHelper_Coalesce 11 | { 12 | [Test] 13 | public void Coalesce_skips_null_strings() 14 | { 15 | Assert.That(StringHelper.Coalesce(null, null, "test"), Is.EqualTo("test")); 16 | } 17 | 18 | [Test] 19 | public void Coalesce_skips_empty_strings() 20 | { 21 | Assert.That(StringHelper.Coalesce("", "", "test"), Is.EqualTo("test")); 22 | } 23 | 24 | [Test] 25 | public void Coalesce_returns_first_non_null() 26 | { 27 | Assert.That(StringHelper.Coalesce("test", "hello"), Is.EqualTo("test")); 28 | } 29 | 30 | [Test] 31 | public void Coalesce_returns_null_if_all_are_null() 32 | { 33 | Assert.IsNull(StringHelper.Coalesce(null, null)); 34 | } 35 | 36 | [Test] 37 | public void Coalesce_returns_null_if_all_are_empty() 38 | { 39 | Assert.IsNull(StringHelper.Coalesce("", "")); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Utils.Tests/Utils.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0;net5.0 5 | 6 | 7 | 8 | 7 9 | 10 | 11 | 12 | 7 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | all 22 | runtime; build; native; contentfiles; analyzers; buildtransitive 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /Utils.Tests/Linq/EnumerableExtensions_Random.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Impworks.Utils.Linq; 3 | using NUnit.Framework; 4 | 5 | namespace Utils.Tests.Linq 6 | { 7 | /// 8 | /// Tests for random-based methods in EnumerableExtensions. 9 | /// 10 | [TestFixture] 11 | public class EnumerableExtensions_Random 12 | { 13 | [Test] 14 | public void Shuffle_shuffles_array() 15 | { 16 | var src = Enumerable.Range(1, 100).ToList(); 17 | 18 | RandomTestHelper.AtLeastOnce(() => 19 | { 20 | var result = src.Shuffle().ToList(); 21 | return result.SequenceEqual(src) == false 22 | && result.OrderBy(x => x).SequenceEqual(src) == true; 23 | }); 24 | } 25 | 26 | [Test] 27 | public void PickRandom_picks_elements() 28 | { 29 | var src = Enumerable.Range(1, 100).ToList(); 30 | 31 | RandomTestHelper.AtLeastOnce(() => 32 | { 33 | var a = src.PickRandom(); 34 | var b = src.PickRandom(); 35 | return src.Contains(a) 36 | && src.Contains(b) 37 | && a != b; 38 | }); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Utils.Tests/Strings/StringExtensions_ValueOrNull.cs: -------------------------------------------------------------------------------- 1 | using Impworks.Utils.Strings; 2 | using NUnit.Framework; 3 | 4 | namespace Utils.Tests.Strings 5 | { 6 | /// 7 | /// Tests for StringExtensions.ValueOrNull. 8 | /// 9 | [TestFixture] 10 | public class StringExtensions_ValueOrNull 11 | { 12 | [Test] 13 | public void ValueOrNull_returns_value_for_non_empty_string() 14 | { 15 | Assert.That("Test".ValueOrNull(), Is.EqualTo("Test")); 16 | } 17 | 18 | [Test] 19 | public void ValueOrNull_returns_value_for_whitespace_string_if_flag_is_enabled() 20 | { 21 | Assert.That(" ".ValueOrNull(allowWhitespace: true), Is.EqualTo(" ")); 22 | } 23 | 24 | [Test] 25 | public void ValueOrNull_returns_null_for_whitespace_string() 26 | { 27 | Assert.IsNull(" ".ValueOrNull()); 28 | } 29 | 30 | [Test] 31 | public void ValueOrNull_returns_null_for_empty_string() 32 | { 33 | Assert.IsNull("".ValueOrNull()); 34 | } 35 | 36 | [Test] 37 | public void ValueOrNull_returns_null_for_null_string() 38 | { 39 | Assert.IsNull((null as string).ValueOrNull()); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Utils.Tests/Linq/ExprHelper_And.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Impworks.Utils.Linq; 3 | using NUnit.Framework; 4 | 5 | namespace Utils.Tests.Linq 6 | { 7 | /// 8 | /// Tests for ExprHelper.And method. 9 | /// 10 | [TestFixture] 11 | public class ExprHelper_And 12 | { 13 | private static IEnumerable<(SampleObject, bool)>AndTestCases() 14 | { 15 | yield return (new SampleObject { Value = 1, Str = "foo" }, true); 16 | yield return (new SampleObject { Value = 1, Str = "foo2" }, false); 17 | yield return (new SampleObject { Value = 2, Str = "foo" }, false); 18 | yield return (new SampleObject { Value = 1, Str = "foo", Field = "foo" }, true); 19 | yield return (new SampleObject { Value = 1, Str = "foo", Field = "x" }, false); 20 | } 21 | 22 | [Test] 23 | [TestCaseSource(typeof(ExprHelper_And), nameof(AndTestCases))] 24 | public void Expression_compiles((SampleObject obj, bool result) arg) 25 | { 26 | var pred = ExprHelper.And( 27 | x => x.Value == 1, 28 | x => x.Str == "foo", 29 | x => x.Field == null || x.Field.Length > 2 30 | ).Compile(); 31 | 32 | Assert.That(pred(arg.obj), Is.EqualTo(arg.result)); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Utils/Linq/EnumerableExtensions.Random.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.CompilerServices; 5 | using Impworks.Utils.Random; 6 | 7 | namespace Impworks.Utils.Linq; 8 | 9 | public static partial class EnumerableExtensions 10 | { 11 | /// 12 | /// Returns the sequence of the same items in random order. 13 | /// 14 | public static IEnumerable Shuffle(this IEnumerable source) 15 | { 16 | return source.Select(x => new { Value = x, Random = RandomHelper.Float() }) 17 | .OrderBy(x => x.Random) 18 | .Select(x => x.Value); 19 | } 20 | 21 | /// 22 | /// Picks a random element from the collection. 23 | /// 24 | /// Source collection. 25 | /// 26 | /// Weight function. Elements with bigger weight are more likely to be selected. 27 | /// If not specified, all elements have equal weights. 28 | /// 29 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 30 | public static T PickRandom(this IReadOnlyList source, Func weightFunc = null) 31 | { 32 | if (weightFunc != null) 33 | return RandomHelper.PickWeighted(source, weightFunc); 34 | 35 | return RandomHelper.Pick(source); 36 | } 37 | } -------------------------------------------------------------------------------- /Utils/Utils.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0;net451;net6 5 | Impworks.Utils 6 | true 7 | Impworks 8 | 9 | Impworks.Utils 10 | https://github.com/impworks/utils 11 | https://github.com/impworks/utils 12 | helpers, utils 13 | A personal collection of helper utility methods. 14 | Impworks.Utils 15 | Impworks.Utils 16 | 17 | 18 | 19 | latest 20 | 21 | 22 | 23 | NETCORE;NETSTANDARD;NETSTANDARD2_0 24 | 25 | 26 | 27 | bin\Release\net451\Impworks.Utils.xml 28 | 29 | 30 | 31 | bin\Release\netstandard2.0\Impworks.Utils.xml 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /Utils.Tests/Exceptions/Try_Do.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Impworks.Utils.Exceptions; 4 | using NUnit.Framework; 5 | 6 | namespace Utils.Tests.Exceptions 7 | { 8 | /// 9 | /// Tests for Try.Do and Try.DoAsync. 10 | /// 11 | [TestFixture] 12 | public class Try_Do 13 | { 14 | #region Sync 15 | 16 | [Test] 17 | public void Executes_action_sync() 18 | { 19 | var foo = false; 20 | 21 | Try.Do(() => foo = true); 22 | 23 | Assert.That(foo, Is.EqualTo(true)); 24 | } 25 | 26 | [Test] 27 | public void Catches_exceptions_sync() 28 | { 29 | Assert.DoesNotThrow(() => Try.Do(() => throw new Exception())); 30 | } 31 | 32 | #endregion 33 | 34 | #region Async 35 | 36 | [Test] 37 | public async Task Executes_action_async() 38 | { 39 | var foo = false; 40 | 41 | await Try.DoAsync(async () => 42 | { 43 | await Task.Yield(); 44 | foo = true; 45 | }); 46 | 47 | Assert.That(foo, Is.EqualTo(true)); 48 | } 49 | 50 | [Test] 51 | public void Catches_exceptions_async() 52 | { 53 | Assert.DoesNotThrowAsync( 54 | async () => await Try.DoAsync(async () => 55 | { 56 | await Task.Yield(); 57 | throw new Exception(); 58 | }) 59 | ); 60 | } 61 | 62 | #endregion 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Utils/Linq/EnumerableExtensions.Recursive.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Impworks.Utils.Linq; 5 | 6 | public static partial class EnumerableExtensions 7 | { 8 | /// 9 | /// Applies an action to all items in the tree. 10 | /// 11 | /// Root list of items. 12 | /// Function that selects children of a node. 13 | /// Action to perform on all children. 14 | public static void ApplyRecursively(this IEnumerable objects, Func> childSelector, Action action) 15 | { 16 | if (objects == null) 17 | return; 18 | 19 | foreach (var obj in objects) 20 | { 21 | action(obj); 22 | childSelector(obj).ApplyRecursively(childSelector, action); 23 | } 24 | } 25 | 26 | /// 27 | /// Selects all tree items into a single flat list. 28 | /// 29 | /// Root list of items. 30 | /// Function that selects children of a single node. 31 | public static IEnumerable SelectRecursively(this IEnumerable objects, Func> childSelector) 32 | { 33 | if (objects == null) 34 | yield break; 35 | 36 | foreach (var obj in objects) 37 | { 38 | yield return obj; 39 | 40 | var children = childSelector(obj); 41 | foreach (var child in children.SelectRecursively(childSelector)) 42 | yield return child; 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /Utils.Tests/Strings/StringExtensions_Capitalize.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Impworks.Utils.Strings; 3 | using NUnit.Framework; 4 | 5 | namespace Utils.Tests.Strings 6 | { 7 | /// 8 | /// Tests for StringExtensions.Capitalize. 9 | /// 10 | [TestFixture] 11 | public class StringExtensions_Capitalize 12 | { 13 | [Test] 14 | public void Capitalize_converts_first_character_to_uppercase() 15 | { 16 | Assert.That("hello".Capitalize(), Is.EqualTo("Hello")); 17 | } 18 | 19 | [Test] 20 | public void Capitalize_converts_first_character_to_uppercase_in_russian() 21 | { 22 | Assert.That("привет".Capitalize(), Is.EqualTo("Привет")); 23 | } 24 | 25 | [Test] 26 | public void Capitalize_does_not_affect_special_chars() 27 | { 28 | Assert.That("123".Capitalize(), Is.EqualTo("123")); 29 | } 30 | 31 | [Test] 32 | public void Capitalize_does_not_affect_already_uppercase_chars() 33 | { 34 | Assert.That("TEST".Capitalize(), Is.EqualTo("TEST")); 35 | } 36 | 37 | [Test] 38 | public void Capitalize_does_not_affect_empty_strings() 39 | { 40 | Assert.That("".Capitalize(), Is.EqualTo("")); 41 | } 42 | 43 | [Test] 44 | public void Capitalize_throws_on_null_string() 45 | { 46 | Assert.Throws(() => (null as string).Capitalize()); 47 | } 48 | 49 | [Test] 50 | public void Capitalize_throws_on_null_culture() 51 | { 52 | Assert.Throws(() => "hello".Capitalize(null)); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Utils.Tests/Xml/XmlHelper_Deserialize.cs: -------------------------------------------------------------------------------- 1 | using Impworks.Utils.Xml; 2 | using Newtonsoft.Json; 3 | using NUnit.Framework; 4 | using Utils.Tests.Linq; 5 | 6 | namespace Utils.Tests.Xml 7 | { 8 | /// 9 | /// Tests for XmlHelper.Deserialize. 10 | /// 11 | [TestFixture] 12 | public class XmlHelper_Deserialize 13 | { 14 | SampleObject GetObj(bool useNull = false) 15 | { 16 | return new SampleObject(1, useNull ? null : "hello"); 17 | } 18 | 19 | [Test] 20 | public void Deserialize_can_deserialize_from_clean_mode() 21 | { 22 | var obj = GetObj(); 23 | var xml = XmlHelper.Serialize(obj); 24 | var newObj = XmlHelper.Deserialize(xml); 25 | Assert.That(JsonConvert.SerializeObject(newObj), Is.EqualTo(JsonConvert.SerializeObject(obj))); 26 | } 27 | 28 | [Test] 29 | public void Deserialize_can_deserialize_from_unclean_mode() 30 | { 31 | var obj = GetObj(); 32 | var xml = XmlHelper.Serialize(obj, clean: false); 33 | var newObj = XmlHelper.Deserialize(xml); 34 | Assert.That(JsonConvert.SerializeObject(newObj), Is.EqualTo(JsonConvert.SerializeObject(obj))); 35 | } 36 | 37 | [Test] 38 | public void Deserialize_can_deserialize_with_explicit_name() 39 | { 40 | var obj = GetObj(); 41 | var name = "FooBar"; 42 | var xml = XmlHelper.Serialize(obj, rootName: name); 43 | var newObj = XmlHelper.Deserialize(xml, rootName: name); 44 | Assert.That(JsonConvert.SerializeObject(newObj), Is.EqualTo(JsonConvert.SerializeObject(obj))); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Utils/Linq/EnumerableExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.CompilerServices; 5 | 6 | namespace Impworks.Utils.Linq; 7 | 8 | public static partial class EnumerableExtensions 9 | { 10 | /// 11 | /// Chainable method for joining a list of elements with a separator. 12 | /// 13 | /// List of elements. 14 | /// Separator to place between elements. 15 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 16 | public static string JoinString(this IEnumerable source, string separator) 17 | { 18 | return string.Join(separator, source); 19 | } 20 | 21 | #if !NET6_0_OR_GREATER 22 | /// 23 | /// Excludes duplicate values from the list (specified by an expression). 24 | /// 25 | /// Original sequence. 26 | /// Element projection that returns must-be-unique values. 27 | public static IEnumerable DistinctBy(this IEnumerable sequence, Func idGetter) 28 | { 29 | var hashSet = new HashSet(); 30 | return sequence.Where(x => hashSet.Add(idGetter(x))); 31 | } 32 | #endif 33 | 34 | /// 35 | /// Adds a list of items into the collection. 36 | /// 37 | /// Target collection. 38 | /// Elements to add to the collection. 39 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 40 | public static void AddRange(this ICollection collection, IEnumerable data) 41 | { 42 | foreach (var item in data) 43 | collection.Add(item); 44 | } 45 | } -------------------------------------------------------------------------------- /Utils.Tests/Comparisons/CompareHelper_Max.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Impworks.Utils.Comparisons; 3 | using NUnit.Framework; 4 | 5 | namespace Utils.Tests.Comparisons 6 | { 7 | /// 8 | /// Tests for CompareHelper.Max. 9 | /// 10 | [TestFixture] 11 | public class CompareHelper_Max 12 | { 13 | [Test] 14 | public void Returns_max_of_two_strings() 15 | { 16 | Assert.That(CompareHelper.Max("a", "b"), Is.EqualTo("b")); 17 | } 18 | 19 | [Test] 20 | public void Returns_max_of_many_strings() 21 | { 22 | Assert.That(CompareHelper.Max("a", "b", "z", "_", "y"), Is.EqualTo("z")); 23 | } 24 | 25 | [Test] 26 | public void Returns_max_of_two_dates() 27 | { 28 | var a = DateTime.Parse("2023-01-01"); 29 | var b = DateTime.Parse("2023-02-01"); 30 | Assert.That(CompareHelper.Max(a, b), Is.EqualTo(b)); 31 | } 32 | 33 | [Test] 34 | public void Returns_max_of_many_dates() 35 | { 36 | var dates = new[] 37 | { 38 | DateTime.Parse("2023-01-01"), 39 | DateTime.Parse("2023-02-01"), 40 | DateTime.Parse("2021-10-12"), 41 | DateTime.Parse("2022-07-11"), 42 | }; 43 | Assert.That(CompareHelper.Max(dates), Is.EqualTo(dates[1])); 44 | } 45 | 46 | [Test] 47 | public void Returns_single_item() 48 | { 49 | Assert.That(CompareHelper.Max("a"), Is.EqualTo("a")); 50 | } 51 | 52 | [Test] 53 | public void Throws_exception_for_empty_array() 54 | { 55 | Assert.Throws(() => CompareHelper.Max()); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Utils.Tests/Comparisons/CompareHelper_Min.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Impworks.Utils.Comparisons; 3 | using NUnit.Framework; 4 | 5 | namespace Utils.Tests.Comparisons 6 | { 7 | /// 8 | /// Tests for CompareHelper.Min. 9 | /// 10 | [TestFixture] 11 | public class CompareHelper_Min 12 | { 13 | [Test] 14 | public void Returns_min_of_two_strings() 15 | { 16 | Assert.That(CompareHelper.Min("a", "b"), Is.EqualTo("a")); 17 | } 18 | 19 | [Test] 20 | public void Returns_min_of_many_strings() 21 | { 22 | Assert.That(CompareHelper.Min("a", "b", "z", "_", "y"), Is.EqualTo("_")); 23 | } 24 | 25 | [Test] 26 | public void Returns_min_of_two_dates() 27 | { 28 | var a = DateTime.Parse("2023-01-01"); 29 | var b = DateTime.Parse("2023-02-01"); 30 | Assert.That(CompareHelper.Min(a, b), Is.EqualTo(a)); 31 | } 32 | 33 | [Test] 34 | public void Returns_min_of_many_dates() 35 | { 36 | var dates = new[] 37 | { 38 | DateTime.Parse("2023-01-01"), 39 | DateTime.Parse("2023-02-01"), 40 | DateTime.Parse("2021-10-12"), 41 | DateTime.Parse("2022-07-11"), 42 | }; 43 | Assert.That(CompareHelper.Min(dates), Is.EqualTo(dates[2])); 44 | } 45 | 46 | [Test] 47 | public void Returns_single_item() 48 | { 49 | Assert.That(CompareHelper.Min("a"), Is.EqualTo("a")); 50 | } 51 | 52 | [Test] 53 | public void Throws_exception_for_empty_array() 54 | { 55 | Assert.Throws(() => CompareHelper.Min()); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Utils.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27428.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Utils", "Utils\Utils.csproj", "{3D523CE2-11E0-41FE-BB4C-AE6CC031D644}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Utils.Tests", "Utils.Tests\Utils.Tests.csproj", "{267FB804-D9A3-4DBB-AF4B-24AEE0FC4DF6}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{FFCA7961-E081-4637-8BF6-9DC2CB1A8B10}" 11 | ProjectSection(SolutionItems) = preProject 12 | README.md = README.md 13 | EndProjectSection 14 | EndProject 15 | Global 16 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 17 | Debug|Any CPU = Debug|Any CPU 18 | Release|Any CPU = Release|Any CPU 19 | EndGlobalSection 20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 21 | {3D523CE2-11E0-41FE-BB4C-AE6CC031D644}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 22 | {3D523CE2-11E0-41FE-BB4C-AE6CC031D644}.Debug|Any CPU.Build.0 = Debug|Any CPU 23 | {3D523CE2-11E0-41FE-BB4C-AE6CC031D644}.Release|Any CPU.ActiveCfg = Release|Any CPU 24 | {3D523CE2-11E0-41FE-BB4C-AE6CC031D644}.Release|Any CPU.Build.0 = Release|Any CPU 25 | {267FB804-D9A3-4DBB-AF4B-24AEE0FC4DF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 | {267FB804-D9A3-4DBB-AF4B-24AEE0FC4DF6}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 | {267FB804-D9A3-4DBB-AF4B-24AEE0FC4DF6}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {267FB804-D9A3-4DBB-AF4B-24AEE0FC4DF6}.Release|Any CPU.Build.0 = Release|Any CPU 29 | EndGlobalSection 30 | GlobalSection(SolutionProperties) = preSolution 31 | HideSolutionNode = FALSE 32 | EndGlobalSection 33 | GlobalSection(ExtensibilityGlobals) = postSolution 34 | SolutionGuid = {23D8BB45-E681-4A68-B899-CA5848944D72} 35 | EndGlobalSection 36 | EndGlobal 37 | -------------------------------------------------------------------------------- /Utils.Tests/Linq/EnumerableExtensions_Recursive.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Impworks.Utils.Linq; 4 | using NUnit.Framework; 5 | 6 | namespace Utils.Tests.Linq 7 | { 8 | /// 9 | /// Tests for EnumerableExtensions.*Recursive methods. 10 | /// 11 | [TestFixture] 12 | public class EnumerableExtensions_Recursive 13 | { 14 | private SampleTreeNode[] GetTree() 15 | { 16 | return new[] 17 | { 18 | new SampleTreeNode( 19 | 1, 20 | new SampleTreeNode( 21 | 2, 22 | new SampleTreeNode(3), 23 | new SampleTreeNode(4) 24 | ), 25 | new SampleTreeNode( 26 | 5, 27 | new SampleTreeNode( 28 | 6, 29 | new SampleTreeNode(7) 30 | ), 31 | new SampleTreeNode(8) 32 | ) 33 | ), 34 | }; 35 | } 36 | 37 | [Test] 38 | public void SelectRecursive_selects_all_tree_nodes_in_depth_first_order() 39 | { 40 | var tree = GetTree(); 41 | var result = tree.SelectRecursively(x => x.Children).Select(x => x.Value); 42 | 43 | Assert.That(result, Is.EqualTo(new [] { 1, 2, 3, 4, 5, 6, 7, 8 })); 44 | } 45 | 46 | [Test] 47 | public void ApplyRecursive_visits_all_tree_nodes_in_depth_first_order() 48 | { 49 | var tree = GetTree(); 50 | var result = new List(); 51 | tree.ApplyRecursively(x => x.Children, x => result.Add(x.Value)); 52 | 53 | Assert.That(result, Is.EqualTo(new [] { 1, 2, 3, 4, 5, 6, 7, 8 })); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Utils/Comparisons/CompareHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Impworks.Utils.Comparisons; 5 | 6 | /// 7 | /// Helper methods to work with comparable values. 8 | /// 9 | public class CompareHelper 10 | { 11 | /// 12 | /// General comparator that returns the minimal value of the two. 13 | /// 14 | public static T Min(T a, T b) where T : IComparable 15 | { 16 | var cmp = Comparer.Default; 17 | return cmp.Compare(a, b) < 0 ? a : b; 18 | } 19 | 20 | /// 21 | /// General comparator that returns the minimal value of the list. 22 | /// 23 | public static T Min(params T[] elems) where T : IComparable 24 | { 25 | if (elems.Length == 0) 26 | throw new ArgumentException("At least 1 element is expected"); 27 | 28 | var cmp = Comparer.Default; 29 | var min = elems[0]; 30 | 31 | for (var i = 1; i < elems.Length; i++) 32 | if (cmp.Compare(elems[i], min) < 0) 33 | min = elems[i]; 34 | 35 | return min; 36 | } 37 | 38 | /// 39 | /// General comparator that returns the maximal value of the two. 40 | /// 41 | public static T Max(T a, T b) where T : IComparable 42 | { 43 | var cmp = Comparer.Default; 44 | return cmp.Compare(a, b) > 0 ? a : b; 45 | } 46 | 47 | /// 48 | /// General comparator that returns the maximal value of the list. 49 | /// 50 | public static T Max(params T[] elems) where T : IComparable 51 | { 52 | if (elems.Length == 0) 53 | throw new ArgumentException("At least 1 element is expected"); 54 | 55 | var cmp = Comparer.Default; 56 | var max = elems[0]; 57 | 58 | for (var i = 1; i < elems.Length; i++) 59 | if (cmp.Compare(elems[i], max) > 0) 60 | max = elems[i]; 61 | 62 | return max; 63 | } 64 | } -------------------------------------------------------------------------------- /Utils.Tests/Strings/StringExtensions_EndsWithPart.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Impworks.Utils.Strings; 3 | using NUnit.Framework; 4 | 5 | namespace Utils.Tests.Strings 6 | { 7 | /// 8 | /// Tests for EndsWithPart methods. 9 | /// 10 | [TestFixture] 11 | public class StringExtensions_EndsWithPart 12 | { 13 | [Test] 14 | public void EndsWithPart_returns_true_if_starts_with_part() 15 | { 16 | Assert.IsTrue("hello world".EndsWithPart("test world", 5)); 17 | } 18 | 19 | [Test] 20 | public void EndsWithPart_returns_false_if_does_not_start_with_part() 21 | { 22 | Assert.IsFalse("hello world".EndsWithPart("test there", 5)); 23 | } 24 | 25 | [Test] 26 | public void EndsWithPart_returns_true_if_both_strings_are_shorter_but_equal() 27 | { 28 | Assert.IsTrue("hello".EndsWithPart("hello", 10)); 29 | } 30 | 31 | [Test] 32 | public void EndsWithPart_returns_false_if_any_string_is_shorter_but_not_equal() 33 | { 34 | Assert.IsFalse("hello".EndsWithPart("test", 10)); 35 | } 36 | 37 | [Test] 38 | public void EndsWithPart_is_case_sensitive_by_default() 39 | { 40 | Assert.IsFalse("hello world".EndsWithPart("hello WORLD", 5)); 41 | } 42 | 43 | [Test] 44 | public void EndsWithPart_ignores_case_if_flag_is_specified() 45 | { 46 | Assert.IsTrue("hello world".EndsWithPart("hello WORLD", 5, true)); 47 | } 48 | 49 | [Test] 50 | [TestCase(null, null, true)] 51 | [TestCase(null, "a", false)] 52 | [TestCase("a", null, false)] 53 | public void EndsWithPart_handles_nulls(string left, string right, bool result) 54 | { 55 | Assert.That(result, Is.EqualTo(left.EndsWithPart(right, 5))); 56 | } 57 | 58 | [Test] 59 | public void EndsWithPart_throws_on_zero_compareLength() 60 | { 61 | Assert.Throws(() => "a".EndsWithPart("b", 0)); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Utils.Tests/Strings/StringExtensions_StartsWithPart.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Impworks.Utils.Strings; 3 | using NUnit.Framework; 4 | 5 | namespace Utils.Tests.Strings 6 | { 7 | /// 8 | /// Tests for StartsWithPart methods. 9 | /// 10 | [TestFixture] 11 | public class StringExtensions_StartsWithPart 12 | { 13 | [Test] 14 | public void StartsWithPath_returns_true_if_starts_with_part() 15 | { 16 | Assert.IsTrue("hello world".StartsWithPart("hello there", 5)); 17 | } 18 | 19 | [Test] 20 | public void StartsWithPath_returns_false_if_does_not_start_with_part() 21 | { 22 | Assert.IsFalse("hello world".StartsWithPart("test there", 5)); 23 | } 24 | 25 | [Test] 26 | public void StartsWithPath_returns_true_if_both_strings_are_shorter_but_equal() 27 | { 28 | Assert.IsTrue("hello".StartsWithPart("hello", 10)); 29 | } 30 | 31 | [Test] 32 | public void StartsWithPath_returns_false_if_any_string_is_shorter_but_not_equal() 33 | { 34 | Assert.IsFalse("hello".StartsWithPart("test", 10)); 35 | } 36 | 37 | [Test] 38 | public void StartsWithPath_is_case_sensitive_by_default() 39 | { 40 | Assert.IsFalse("hello world".StartsWithPart("HELLO there", 5)); 41 | } 42 | 43 | [Test] 44 | public void StartsWithPath_ignores_case_if_flag_is_specified() 45 | { 46 | Assert.IsTrue("hello world".StartsWithPart("HELLO there", 5, true)); 47 | } 48 | 49 | [Test] 50 | [TestCase(null, null, true)] 51 | [TestCase(null, "a", false)] 52 | [TestCase("a", null, false)] 53 | public void StartsWithPath_handles_nulls(string left, string right, bool result) 54 | { 55 | Assert.That(result, Is.EqualTo(left.StartsWithPart(right, 5))); 56 | } 57 | 58 | [Test] 59 | public void StartsWithPart_throws_on_zero_compareLength() 60 | { 61 | Assert.Throws(() => "a".StartsWithPart("b", 0)); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Utils.Tests/Linq/ExprHelper_Or.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Impworks.Utils.Linq; 4 | using NUnit.Framework; 5 | 6 | namespace Utils.Tests.Linq 7 | { 8 | /// 9 | /// Tests for ExprHelper.Or method. 10 | /// 11 | [TestFixture] 12 | public class ExprHelper_Or 13 | { 14 | private static IEnumerable<(SampleObject, bool)> OrTestCases() 15 | { 16 | yield return (new SampleObject { Value = 1, Str = "foo" }, true); 17 | yield return (new SampleObject { Value = 0, Str = "foo" }, true); 18 | yield return (new SampleObject { Value = 1, Str = "foo", Field = "foo" }, true); 19 | yield return (new SampleObject { Value = 0, Str = "x" }, false); 20 | yield return (new SampleObject { Value = 0, Str = "x", Field = "x" }, false); 21 | } 22 | 23 | [Test] 24 | [TestCaseSource(typeof(ExprHelper_Or), nameof(OrTestCases))] 25 | public void Expression_compiles((SampleObject obj, bool result) arg) 26 | { 27 | var pred = ExprHelper.Or( 28 | x => x.Value == 1, 29 | x => x.Str == "foo", 30 | x => x.Field != null && x.Field.Length > 2 31 | ).Compile(); 32 | 33 | Assert.That(pred(arg.obj), Is.EqualTo(arg.result)); 34 | } 35 | 36 | [Test] 37 | public void NestedLambda_compiles() 38 | { 39 | var objs = new[] 40 | { 41 | new SampleListObject {Key = "1", Values = new[] {"a", "ab", "abc"}}, 42 | new SampleListObject {Key = "2", Values = new[] {"ab", "abc"}}, 43 | new SampleListObject {Key = "3", Values = new[] {"ab", "abc", "abcd"}}, 44 | new SampleListObject {Key = "4", Values = new[] {"ab"}}, 45 | }; 46 | 47 | var lengths = new[] {1, 4}; 48 | 49 | var pred = ExprHelper.Or( 50 | x => x.Key == "4", 51 | x => x.Values.Any(y => lengths.Contains(y.Length)) 52 | ).Compile(); 53 | 54 | Assert.That(objs.Where(pred).Select(x => x.Key).JoinString(","), Is.EqualTo("1,3,4")); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Utils.Tests/Linq/EnumerableExtensions_OrderBy_IEnumerable.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Impworks.Utils.Linq; 4 | using NUnit.Framework; 5 | 6 | namespace Utils.Tests.Linq 7 | { 8 | /// 9 | /// Tests for EnumerableExtensions.OrderBy's IEnumerable overrides. 10 | /// 11 | [TestFixture] 12 | public class EnumerableExtensions_OrderBy_IEnumerable 13 | { 14 | private IEnumerable GetSampleObjects() 15 | { 16 | yield return new SampleObject(3, "hello"); 17 | yield return new SampleObject(1, "world"); 18 | yield return new SampleObject(2, "abra"); 19 | } 20 | 21 | [Test] 22 | public void OrderBy_orders_ascending_when_flag_is_false() 23 | { 24 | var objs = GetSampleObjects(); 25 | 26 | var result = objs.OrderBy(x => x.Value, false).Select(x => x.Str); 27 | 28 | Assert.That(result, Is.EqualTo(new [] { "world", "abra", "hello" })); 29 | } 30 | 31 | [Test] 32 | public void OrderBy_orders_descending_when_flag_is_true() 33 | { 34 | var objs = GetSampleObjects(); 35 | 36 | var result = objs.OrderBy(x => x.Value, true).Select(x => x.Str); 37 | 38 | Assert.That(result, Is.EqualTo(new [] { "hello", "abra", "world" })); 39 | } 40 | 41 | [Test] 42 | public void ThenBy_orders_ascending_when_flag_is_false() 43 | { 44 | var objs = GetSampleObjects(); 45 | 46 | var result = objs.OrderBy(x => x.Str.Length, false) 47 | .ThenBy(x => x.Value, false) 48 | .Select(x => x.Str); 49 | 50 | Assert.That(result, Is.EqualTo(new [] { "abra", "world", "hello" })); 51 | } 52 | 53 | [Test] 54 | public void ThenBy_orders_ascending_when_flag_is_true() 55 | { 56 | var objs = GetSampleObjects(); 57 | 58 | var result = objs.OrderBy(x => x.Str.Length, false) 59 | .ThenBy(x => x.Value, true) 60 | .Select(x => x.Str); 61 | 62 | Assert.That(result, Is.EqualTo(new [] { "abra", "hello", "world" })); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Utils/Dictionary/DictionaryExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Runtime.CompilerServices; 3 | 4 | namespace Impworks.Utils.Dictionary; 5 | 6 | /// 7 | /// Helper methods for dictionaries. 8 | /// 9 | public static class DictionaryExtensions 10 | { 11 | /// 12 | /// Returns the value if it exists in the dictionary. 13 | /// Otherwise, the default value for the type is returned. 14 | /// 15 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 16 | public static TVal TryGetValue(this IDictionary dict, TKey key) 17 | { 18 | return key != null && dict.TryGetValue(key, out var result) ? result : default; 19 | } 20 | 21 | /// 22 | /// Returns the value for the first key in the list that exists in the dictionary. 23 | /// Otherwise, the default value for the type is returned. 24 | /// 25 | public static TVal TryGetValue(this IDictionary dict, params TKey[] keys) 26 | { 27 | if(keys != null) 28 | foreach (var key in keys) 29 | if (key != null && dict.TryGetValue(key, out var result)) 30 | return result; 31 | 32 | return default; 33 | } 34 | 35 | /// 36 | /// Returns the value if it exists in the dictionary. 37 | /// If no keys exist, null is returned. 38 | /// 39 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 40 | public static TVal? TryGetNullableValue(this IDictionary dict, TKey key) 41 | where TVal: struct 42 | { 43 | return key != null && dict.TryGetValue(key, out var result) ? result : null; 44 | } 45 | 46 | /// 47 | /// Returns the value for the first key in the list that exists in the dictionary. 48 | /// If no keys exist, null is returned. 49 | /// 50 | public static TVal? TryGetNullableValue(this IDictionary dict, params TKey[] keys) 51 | where TVal: struct 52 | { 53 | if(keys != null) 54 | foreach (var key in keys) 55 | if (key != null && dict.TryGetValue(key, out var result)) 56 | return result; 57 | 58 | return null; 59 | } 60 | } -------------------------------------------------------------------------------- /Utils.Tests/Tasks/Locker_AcquireAsync.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Impworks.Utils.Linq; 6 | using Impworks.Utils.Tasks; 7 | using NUnit.Framework; 8 | 9 | namespace Utils.Tests.Tasks 10 | { 11 | [TestFixture] 12 | public class Locker_AcquireAsync 13 | { 14 | [Test] 15 | public async Task AcquireAsync_locks_by_id() 16 | { 17 | var locker = new Locker(); 18 | var result = new List(); 19 | 20 | await Task.WhenAll( 21 | Add(1, 0, "a", "b", "c"), 22 | Add(2, 50, "d", "e", "f"), 23 | Add(1, 50, "h", "i", "j") 24 | ); 25 | 26 | Assert.That(result.JoinString(" "), Is.EqualTo("a d b e c f h i j")); 27 | 28 | async Task Add(int key, int initialDelay, params string[] values) 29 | { 30 | await Task.Delay(initialDelay); 31 | 32 | using var _ = await locker.AcquireAsync(key, CancellationToken.None); 33 | 34 | foreach (var v in values) 35 | { 36 | result.Add(v); 37 | await Task.Delay(100); 38 | } 39 | } 40 | } 41 | 42 | [Test] 43 | public async Task AcquireAsync_uses_cancellation_token() 44 | { 45 | var locker = new Locker(); 46 | var result = new List(); 47 | 48 | await Task.WhenAll( 49 | Run("a", 0, 300), 50 | Run("b", 50, 100) 51 | ); 52 | 53 | Assert.That(result.JoinString(" "), Is.EqualTo("ex:b a")); 54 | 55 | async Task Run(string key, int delay, int timeout) 56 | { 57 | await Task.Delay(delay); 58 | var cts = new CancellationTokenSource(timeout); 59 | try 60 | { 61 | using var _ = await locker.AcquireAsync(1, cts.Token); 62 | await Task.Delay(200, CancellationToken.None); 63 | result.Add(key); 64 | } 65 | catch(Exception ex) 66 | { 67 | Assert.That(ex, Is.InstanceOf()); 68 | result.Add("ex:" + key); 69 | } 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Utils/Xml/XmlExtensions.Attributes.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | using System.Xml.Linq; 4 | using Impworks.Utils.Strings; 5 | 6 | namespace Impworks.Utils.Xml; 7 | 8 | /// 9 | /// Helper methods for XML. 10 | /// 11 | public static class XmlExtensions 12 | { 13 | /// 14 | /// Returns the string value of an attribute. 15 | /// 16 | /// XElement with the attribute. 17 | /// Name of the attribute. 18 | /// String value of the attribute, or null. 19 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 20 | public static string Attr(this XElement xml, string name) 21 | { 22 | if(xml == null) 23 | throw new ArgumentNullException(nameof(xml)); 24 | 25 | if(name == null) 26 | throw new ArgumentNullException(nameof(name)); 27 | 28 | return xml.Attribute(name)?.Value; 29 | } 30 | 31 | /// 32 | /// Attempts to parse an attribute's value safely. 33 | /// 34 | /// XElement with the attribute. 35 | /// Name of the attribute. 36 | /// Parser function. If not specified, the default function will be used for built-in types. 37 | /// Converted value of the attribute, or default. 38 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 39 | public static T TryParseAttr(this XElement xml, string name, Func parseFunc = null) 40 | { 41 | return xml.Attr(name).TryParse(parseFunc); 42 | } 43 | 44 | /// 45 | /// Attempts to parse an attribute's value safely. 46 | /// 47 | /// XElement with the attribute. 48 | /// Name of the attribute. 49 | /// Parser function. If not specified, the default function will be used for built-in types. 50 | /// Converted value of the attribute, or default. 51 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 52 | public static T ParseAttr(this XElement xml, string name, Func parseFunc = null) 53 | { 54 | var value = xml.Attr(name) 55 | ?? throw new ArgumentException($"Attribute '{name}' does not exist.", nameof(name)); 56 | 57 | return value.Parse(parseFunc); 58 | } 59 | } -------------------------------------------------------------------------------- /Utils.Tests/Linq/ExprHelper_Apply.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Impworks.Utils.Linq; 3 | using NUnit.Framework; 4 | 5 | namespace Utils.Tests.Linq 6 | { 7 | /// 8 | /// Tests for ExprHelper.Apply method. 9 | /// 10 | [TestFixture] 11 | public class ExprHelper_Apply 12 | { 13 | [Test] 14 | public void Func1() 15 | { 16 | var expr = ExprHelper.Apply(x => x, 1).Compile(); 17 | Assert.That(expr(), Is.EqualTo(1)); 18 | } 19 | 20 | [Test] 21 | public void Func2() 22 | { 23 | var expr = ExprHelper.Apply((int x, int y) => x + y, 1).Compile(); 24 | Assert.That(expr(2), Is.EqualTo(3)); 25 | } 26 | 27 | [Test] 28 | public void Func3() 29 | { 30 | var expr = ExprHelper.Apply((int x, int y, int z) => x + y + z, 1).Compile(); 31 | Assert.That(expr(2, 3), Is.EqualTo(6)); 32 | } 33 | 34 | [Test] 35 | public void Func3_null() 36 | { 37 | var expr = ExprHelper.Apply((string x, string y, string z) => x + y + z, null).Compile(); 38 | Assert.That(expr("foo", "bar"), Is.EqualTo("foobar")); 39 | } 40 | 41 | [Test] 42 | public void Action1() 43 | { 44 | var result = 0; 45 | Action set = x => result = x; 46 | ExprHelper.Apply(x => set(x), 1).Compile()(); 47 | Assert.That(result, Is.EqualTo(1)); 48 | } 49 | 50 | [Test] 51 | public void Action2() 52 | { 53 | var result = 0; 54 | Action set = x => result = x; 55 | ExprHelper.Apply((int x, int y) => set(x + y), 1).Compile()(2); 56 | Assert.That(result, Is.EqualTo(3)); 57 | } 58 | 59 | [Test] 60 | public void Action3() 61 | { 62 | var result = 0; 63 | Action set = x => result = x; 64 | ExprHelper.Apply((int x, int y, int z) => set(x + y + z), 1).Compile()(2, 3); 65 | Assert.That(result, Is.EqualTo(6)); 66 | } 67 | 68 | [Test] 69 | public void Action3_null() 70 | { 71 | var result = ""; 72 | Action set = x => result = x; 73 | ExprHelper.Apply((string x, string y, string z) => set(x + y + z), null).Compile()("foo", "bar"); 74 | Assert.That(result, Is.EqualTo("foobar")); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Utils/Strings/StringExtensions.Parse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Impworks.Utils.Exceptions; 4 | 5 | namespace Impworks.Utils.Strings; 6 | 7 | public static partial class StringExtensions 8 | { 9 | /// 10 | /// Parses the value to a type using default parsers. 11 | /// Throws an exception if the value cannot be parsed. 12 | /// 13 | /// Source string. 14 | /// Parser function that creates a value from the string. 15 | public static T Parse(this string str, Func parseFunc = null) 16 | { 17 | if(str == null) 18 | throw new ArgumentNullException(nameof(str)); 19 | 20 | var func = parseFunc ?? StringHelper.GetParseFunction(); 21 | return func(str); 22 | } 23 | 24 | /// 25 | /// Attempts to parse a string using the parse function. 26 | /// Returns the default value if an exception has been raised. 27 | /// 28 | /// Source string. 29 | /// Parser function that creates a value from the string. Default parser is used for built-in types. 30 | public static T TryParse(this string str, Func parseFunc = null) 31 | { 32 | var func = parseFunc ?? StringHelper.GetParseFunction(); 33 | return Try.Get(() => str != null ? func(str) : default); 34 | } 35 | 36 | /// 37 | /// Converts a string with delimiter-separated value representations to a list of values. 38 | /// 39 | /// Splittable string. 40 | /// Sequence of characters that delimits values in the string. Defaults to a comma. 41 | /// Parser function that creates a value from the string part. Default parser is used for built-in types. 42 | public static IReadOnlyList TryParseList(this string str, string separator = ",", Func parseFunc = null) 43 | { 44 | var result = new List(); 45 | 46 | if (str != null) 47 | { 48 | var parts = str.Split([separator], StringSplitOptions.RemoveEmptyEntries); 49 | var func = parseFunc ?? StringHelper.GetParseFunction(); 50 | 51 | foreach (var part in parts) 52 | { 53 | try 54 | { 55 | result.Add(func(part)); 56 | } 57 | catch 58 | { 59 | // do nothing 60 | } 61 | } 62 | } 63 | 64 | return result; 65 | } 66 | } -------------------------------------------------------------------------------- /Utils.Tests/Linq/EnumerableExtensions_Partition.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Impworks.Utils.Linq; 4 | using NUnit.Framework; 5 | 6 | namespace Utils.Tests.Linq 7 | { 8 | /// 9 | /// Tests for EnumerableExtensions.PartitionByX methods. 10 | /// 11 | [TestFixture] 12 | public class EnumerableExtensions_Partition 13 | { 14 | [Test] 15 | public void PartitionBySize_partitions_by_max_batch_size() 16 | { 17 | var src = new[] {1, 2, 3, 4, 5, 6}; 18 | var expected = new[] {new[] {1, 2}, new[] {3, 4}, new[] {5, 6}}; 19 | 20 | Assert.That(src.PartitionBySize(2), Is.EqualTo(expected)); 21 | } 22 | 23 | [Test] 24 | public void PartitionBySize_may_return_smaller_last_partition() 25 | { 26 | var src = new[] {1, 2, 3, 4, 5}; 27 | var expected = new[] {new[] {1, 2}, new[] {3, 4}, new[] {5}}; 28 | 29 | Assert.That(src.PartitionBySize(2), Is.EqualTo(expected)); 30 | } 31 | 32 | [Test] 33 | public void PartitionByCount_return_exact_number_of_batches() 34 | { 35 | for (var i = 1; i < 100; i++) 36 | { 37 | var seq = Enumerable.Range(1, 100).ToList(); 38 | var partitions = seq.PartitionByCount(i); 39 | 40 | Assert.That(partitions.SelectMany(x => x), Is.EqualTo(seq), "Partitioning missed values"); 41 | Assert.That(partitions.Count, Is.EqualTo(i), "Partitioning missed count"); 42 | } 43 | } 44 | 45 | [Test] 46 | public void PartitionByCount_returns_less_batches_if_not_enough_elements() 47 | { 48 | for (var i = 1; i < 100; i++) 49 | { 50 | for (var j = 1; j < 10; j++) 51 | { 52 | var seq = Enumerable.Range(1, i).ToList(); 53 | var partitions = seq.PartitionByCount(i + j); 54 | 55 | Assert.That(partitions.SelectMany(x => x), Is.EqualTo(seq), "Partitioning missed values"); 56 | Assert.That(partitions.Count, Is.EqualTo(i), "Partitioning missed count"); 57 | } 58 | } 59 | } 60 | 61 | [Test] 62 | public void PartitionBySize_requires_size_greater_than_1() 63 | { 64 | Assert.Throws(() => new[] {1, 2, 3}.PartitionBySize(0)); 65 | Assert.Throws(() => new[] {1, 2, 3}.PartitionBySize(-1)); 66 | } 67 | 68 | [Test] 69 | public void PartitionByCount_requires_count_greater_than_1() 70 | { 71 | Assert.Throws(() => new[] {1, 2, 3}.PartitionByCount(0)); 72 | Assert.Throws(() => new[] {1, 2, 3}.PartitionByCount(-1)); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Utils/Tasks/Locker.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using System.Threading; 5 | 6 | namespace Impworks.Utils.Tasks; 7 | 8 | /// 9 | /// Async locker that allows waiting by an ID. 10 | /// 11 | public class Locker 12 | { 13 | public Locker() 14 | { 15 | _locks = new Dictionary(); 16 | } 17 | 18 | private readonly Dictionary _locks; 19 | 20 | /// 21 | /// Acquires a lock, returns an IDisposable that releases it. 22 | /// 23 | public async Task AcquireAsync(T id, CancellationToken token) 24 | { 25 | await WaitAsync(id, token).ConfigureAwait(false); 26 | return new AcquiredLock(() => Release(id)); 27 | } 28 | 29 | /// 30 | /// Waits for the resource to be free. 31 | /// 32 | private Task WaitAsync(T id, CancellationToken token) 33 | { 34 | SemaphoreWrapper s; 35 | lock (_locks) 36 | { 37 | if (!_locks.TryGetValue(id, out s)) 38 | s = _locks[id] = new SemaphoreWrapper(); 39 | } 40 | 41 | return s.WaitAsync(token); 42 | } 43 | 44 | /// 45 | /// Frees the occupied resource. 46 | /// 47 | private void Release(T id) 48 | { 49 | lock (_locks) 50 | { 51 | if (!_locks.TryGetValue(id, out var s)) 52 | return; 53 | 54 | s.Release(); 55 | 56 | if (s.PendingCount == 0) 57 | _locks.Remove(id); 58 | } 59 | } 60 | 61 | /// 62 | /// Helper class for releasing locks. 63 | /// 64 | private class AcquiredLock : IDisposable 65 | { 66 | public AcquiredLock(Action act) 67 | { 68 | _act = act; 69 | } 70 | 71 | private Action _act; 72 | 73 | public void Dispose() 74 | { 75 | _act?.Invoke(); 76 | _act = null; 77 | } 78 | } 79 | 80 | /// 81 | /// Helper class for keeping actual track of pending threads. 82 | /// 83 | private class SemaphoreWrapper 84 | { 85 | public SemaphoreWrapper() 86 | { 87 | PendingCount = 0; 88 | Semaphore = new SemaphoreSlim(1, 1); 89 | } 90 | 91 | public int PendingCount { get; private set; } 92 | public SemaphoreSlim Semaphore { get; } 93 | 94 | public Task WaitAsync(CancellationToken token) 95 | { 96 | PendingCount++; 97 | return Semaphore.WaitAsync(token); 98 | } 99 | 100 | public void Release() 101 | { 102 | Semaphore.Release(); 103 | PendingCount--; 104 | } 105 | } 106 | } -------------------------------------------------------------------------------- /Utils.Tests/Dictionary/DictionaryHelper_TryGetNullableValue.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Impworks.Utils.Dictionary; 3 | using NUnit.Framework; 4 | 5 | namespace Utils.Tests.Dictionary 6 | { 7 | /// 8 | /// Tests for DictionaryHelper.TryGetNullableValue. 9 | /// 10 | [TestFixture] 11 | public class DictionaryHelper_TryGetNullableValue 12 | { 13 | [Test] 14 | public void TryGetNullableValue_returns_value_for_existing_keys() 15 | { 16 | var dict = new Dictionary 17 | { 18 | ["hello"] = 1, 19 | ["a"] = 2 20 | }; 21 | 22 | Assert.That(dict.TryGetNullableValue("a"), Is.EqualTo(2)); 23 | } 24 | 25 | [Test] 26 | public void TryGetNullableValue_returns_default_for_missing_keys() 27 | { 28 | var dict = new Dictionary 29 | { 30 | ["hello"] = 1, 31 | ["a"] = 2 32 | }; 33 | 34 | Assert.That(dict.TryGetNullableValue("bla"), Is.Null); 35 | } 36 | 37 | [Test] 38 | public void TryGetNullableValue_returns_default_for_null_as_single_elem() 39 | { 40 | var dict = new Dictionary 41 | { 42 | ["hello"] = 1, 43 | ["a"] = 2 44 | }; 45 | 46 | Assert.That(dict.TryGetNullableValue(null as string), Is.Null); 47 | } 48 | 49 | [Test] 50 | public void TryGetNullableValue_returns_default_for_null_as_array() 51 | { 52 | var dict = new Dictionary 53 | { 54 | ["hello"] = 1, 55 | ["a"] = 2 56 | }; 57 | 58 | Assert.That(dict.TryGetNullableValue(null as string[]), Is.Null); 59 | } 60 | 61 | [Test] 62 | public void TryGetNullableValue_accepts_list() 63 | { 64 | var dict = new Dictionary 65 | { 66 | [1] = 13, 67 | [2] = 37 68 | }; 69 | 70 | Assert.That(dict.TryGetNullableValue(3, 2, 1), Is.EqualTo(37)); 71 | } 72 | 73 | [Test] 74 | public void TryGetNullableValue_with_list_returns_null_for_missing_keys() 75 | { 76 | var dict = new Dictionary 77 | { 78 | [1] = 1, 79 | [2] = 2 80 | }; 81 | 82 | Assert.That(dict.TryGetNullableValue(4, 5), Is.Null); 83 | } 84 | 85 | [Test] 86 | public void TryGetNullableValue_with_list_skips_null_keys() 87 | { 88 | var dict = new Dictionary 89 | { 90 | ["a"] = 1, 91 | ["b"] = 2 92 | }; 93 | 94 | Assert.That(dict.TryGetNullableValue(null, "b"), Is.EqualTo(2)); 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Utils.Tests/Format/EnumHelper_All.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Impworks.Utils.Format; 3 | using NUnit.Framework; 4 | 5 | namespace Utils.Tests.Format 6 | { 7 | /// 8 | /// Tests for EnumHelper. 9 | /// 10 | [TestFixture] 11 | public class EnumHelper_All 12 | { 13 | [Test] 14 | public void GetEnumDescriptions_generic_returns_lookup_of_descriptions() 15 | { 16 | var result = EnumHelper.GetEnumDescriptions(); 17 | 18 | var expected = new Dictionary 19 | { 20 | [SampleEnum.Hello] = "First value", 21 | [SampleEnum.World] = "Other value", 22 | }; 23 | 24 | Assert.That(result, Is.EqualTo(expected)); 25 | } 26 | 27 | [Test] 28 | public void GetEnumDescriptions_generic_falls_back_to_enum_keys() 29 | { 30 | var result = EnumHelper.GetEnumDescriptions(); 31 | 32 | var expected = new Dictionary 33 | { 34 | [SampleEnum2.Hello] = "Hello", 35 | [SampleEnum2.World] = "World", 36 | }; 37 | 38 | Assert.That(result, Is.EqualTo(expected)); 39 | } 40 | 41 | [Test] 42 | public void GetEnumDescriptions_nongeneric_returns_lookup_of_descriptions() 43 | { 44 | var result = EnumHelper.GetEnumDescriptions(typeof(SampleEnum)); 45 | 46 | var expected = new Dictionary 47 | { 48 | [SampleEnum.Hello] = "First value", 49 | [SampleEnum.World] = "Other value", 50 | }; 51 | 52 | Assert.That(result, Is.EqualTo(expected)); 53 | } 54 | 55 | [Test] 56 | public void GetEnumDescriptions_nongeneric_falls_back_to_enum_keys() 57 | { 58 | var result = EnumHelper.GetEnumDescriptions(typeof(SampleEnum2)); 59 | 60 | var expected = new Dictionary 61 | { 62 | [SampleEnum2.Hello] = "Hello", 63 | [SampleEnum2.World] = "World", 64 | }; 65 | 66 | Assert.That(result, Is.EqualTo(expected)); 67 | } 68 | 69 | [Test] 70 | public void GetEnumDescription_returns_the_value() 71 | { 72 | Assert.That(SampleEnum.Hello.GetEnumDescription(), Is.EqualTo("First value")); 73 | } 74 | 75 | [Test] 76 | public void GetEnumValues_returns_the_array() 77 | { 78 | Assert.That(EnumHelper.GetEnumValues(), Is.EqualTo(new[] {SampleEnum.Hello, SampleEnum.World})); 79 | } 80 | 81 | [Test] 82 | [TestCase("hello")] 83 | [TestCase("HELLO")] 84 | [TestCase("wORlD")] 85 | public void IsDefined_can_ignore_case(string value) 86 | { 87 | Assert.IsTrue(EnumHelper.IsDefined(value, true)); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Utils.Tests/Url/UrlHelper_Combine.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Impworks.Utils.Url; 3 | using NUnit.Framework; 4 | 5 | namespace Utils.Tests.Url 6 | { 7 | /// 8 | /// Tests for UrlHelper. 9 | /// 10 | [TestFixture] 11 | public class UrlHelper_Combine 12 | { 13 | [Test] 14 | public void Combine_collapses_forward_slashes() 15 | { 16 | Assert.That(UrlHelper.Combine("http://a/", "/b/", "/c").ToString(), Is.EqualTo("http://a/b/c")); 17 | } 18 | 19 | [Test] 20 | public void Combine_collapses_backward_slashes() 21 | { 22 | Assert.That(UrlHelper.Combine("http://a/", @"\b\", @"\c").ToString(), Is.EqualTo("http://a/b/c")); 23 | } 24 | 25 | [Test] 26 | public void Combine_adds_missing_slashes() 27 | { 28 | Assert.That(UrlHelper.Combine("http://a", "b", "c").ToString(), Is.EqualTo("http://a/b/c")); 29 | } 30 | 31 | [Test] 32 | public void Combine_adds_missing_slashes_2() 33 | { 34 | Assert.That(UrlHelper.Combine("http://a/b", "c").ToString(), Is.EqualTo("http://a/b/c")); 35 | } 36 | 37 | [Test] 38 | public void Combine_adds_missing_slashes_3() 39 | { 40 | Assert.That(UrlHelper.Combine("http://a/b?", "c").ToString(), Is.EqualTo("http://a/c")); 41 | } 42 | 43 | [Test] 44 | public void Combine_removes_trailing_slash() 45 | { 46 | Assert.That(UrlHelper.Combine("http://a", @"b/").ToString(), Is.EqualTo("http://a/b")); 47 | } 48 | 49 | [Test] 50 | public void Combine_skips_nulls() 51 | { 52 | Assert.That(UrlHelper.Combine("http://a", null, "c").ToString(), Is.EqualTo("http://a/c")); 53 | } 54 | 55 | [Test] 56 | public void Combine_skips_empty_parts() 57 | { 58 | Assert.That(UrlHelper.Combine("http://a", "", "c").ToString(), Is.EqualTo("http://a/c")); 59 | } 60 | 61 | [Test] 62 | public void Combine_skips_whitespace_parts() 63 | { 64 | Assert.That(UrlHelper.Combine("http://a", " ", "c").ToString(), Is.EqualTo("http://a/c")); 65 | } 66 | 67 | [Test] 68 | public void Combine_accepts_empty_parts_array() 69 | { 70 | Assert.That(UrlHelper.Combine("http://a").ToString(), Is.EqualTo("http://a/")); 71 | } 72 | 73 | [Test] 74 | public void Combine_throws_ArgumentNullException_on_null_authority() 75 | { 76 | Assert.Throws(() => UrlHelper.Combine(null)); 77 | } 78 | 79 | [Test] 80 | public void Combine_throws_ArgumentNullException_on_empty_authority() 81 | { 82 | Assert.Throws(() => UrlHelper.Combine("")); 83 | } 84 | 85 | [Test] 86 | public void Combine_throws_ArgumentNullException_on_null_parts() 87 | { 88 | Assert.Throws(() => UrlHelper.Combine("http://123", null)); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Utils.Tests/Url/UrlHelper_GetQuery.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Impworks.Utils.Url; 4 | using Newtonsoft.Json.Linq; 5 | using NUnit.Framework; 6 | 7 | namespace Utils.Tests.Url 8 | { 9 | /// 10 | /// Tests for UrlHelper.GetQuery method group. 11 | /// 12 | [TestFixture] 13 | class UrlHelper_GetQuery 14 | { 15 | [Test] 16 | public void GetQuery_accepts_anonymous_type() 17 | { 18 | var dict = new 19 | { 20 | A = 1, 21 | B = 2 22 | }; 23 | Assert.That(UrlHelper.GetQuery(dict), Is.EqualTo("A=1&B=2")); 24 | } 25 | 26 | [Test] 27 | public void GetQuery_accepts_dictionary() 28 | { 29 | var dict = new Dictionary 30 | { 31 | ["A"] = 1, 32 | ["B"] = 2 33 | }; 34 | Assert.That(UrlHelper.GetQuery(dict), Is.EqualTo("A=1&B=2")); 35 | } 36 | 37 | [Test] 38 | public void GetQuery_accepts_JObject() 39 | { 40 | var dict = new JObject 41 | { 42 | ["A"] = 1, 43 | ["B"] = 2 44 | }; 45 | Assert.That(UrlHelper.GetQuery(dict), Is.EqualTo("A=1&B=2")); 46 | } 47 | 48 | [Test] 49 | public void GetQuery_renders_array_elements() 50 | { 51 | var dict = new 52 | { 53 | A = new [] { 1, 2, 3 } 54 | }; 55 | Assert.That(UrlHelper.GetQuery(dict), Is.EqualTo("A=1&A=2&A=3")); 56 | } 57 | 58 | [Test] 59 | public void GetQuery_renders_strings() 60 | { 61 | var dict = new 62 | { 63 | A = "hello", 64 | B = "world" 65 | }; 66 | Assert.That(UrlHelper.GetQuery(dict), Is.EqualTo("A=hello&B=world")); 67 | } 68 | 69 | [Test] 70 | public void GetQuery_escapes_keys() 71 | { 72 | var dict = new JObject 73 | { 74 | ["A B"] = 1 75 | }; 76 | Assert.That(UrlHelper.GetQuery(dict), Is.EqualTo("A%20B=1")); 77 | } 78 | 79 | [Test] 80 | public void GetQuery_escapes_values() 81 | { 82 | var dict = new JObject 83 | { 84 | ["A"] = "Hello/world" 85 | }; 86 | Assert.That(UrlHelper.GetQuery(dict), Is.EqualTo("A=Hello%2Fworld")); 87 | } 88 | 89 | [Test] 90 | public void Combine_throws_ArgumentNullException_on_null_object() 91 | { 92 | Assert.Throws(() => UrlHelper.GetQuery(null)); 93 | } 94 | 95 | [Test] 96 | public void Combine_throws_ArgumentNullException_on_null_props() 97 | { 98 | Assert.Throws(() => UrlHelper.GetQuery(null as IEnumerable>)); 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /Utils/Tasks/TaskHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | using System.Threading.Tasks; 4 | 5 | namespace Impworks.Utils.Tasks; 6 | #if NETSTANDARD || NET6_0_OR_GREATER 7 | /// 8 | /// Helper methods for working with tasks. 9 | /// 10 | public static class TaskHelper 11 | { 12 | #region GetAll 13 | 14 | /// 15 | /// Returns the result of all tasks awaited in parallel. 16 | /// 17 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 18 | public static async Task> GetAll(Task t1, Task t2) 19 | { 20 | await Task.WhenAll(t1, t2).ConfigureAwait(false); 21 | return (t1.Result, t2.Result); 22 | } 23 | 24 | /// 25 | /// Returns the result of all tasks awaited in parallel. 26 | /// 27 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 28 | public static async Task> GetAll(Task t1, Task t2, Task t3) 29 | { 30 | await Task.WhenAll(t1, t2, t3).ConfigureAwait(false); 31 | return (t1.Result, t2.Result, t3.Result); 32 | } 33 | 34 | /// 35 | /// Returns the result of all tasks awaited in parallel. 36 | /// 37 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 38 | public static async Task> GetAll(Task t1, Task t2, Task t3, Task t4) 39 | { 40 | await Task.WhenAll(t1, t2, t3, t4).ConfigureAwait(false); 41 | return (t1.Result, t2.Result, t3.Result, t4.Result); 42 | } 43 | 44 | /// 45 | /// Returns the result of all tasks awaited in parallel. 46 | /// 47 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 48 | public static async Task> GetAll(Task t1, Task t2, Task t3, Task t4, Task t5) 49 | { 50 | await Task.WhenAll(t1, t2, t3, t4, t5).ConfigureAwait(false); 51 | return (t1.Result, t2.Result, t3.Result, t4.Result, t5.Result); 52 | } 53 | 54 | /// 55 | /// Returns the result of all tasks awaited in parallel. 56 | /// 57 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 58 | public static async Task> GetAll(Task t1, Task t2, Task t3, Task t4, Task t5, Task t6) 59 | { 60 | await Task.WhenAll(t1, t2, t3, t4, t5, t6).ConfigureAwait(false); 61 | return (t1.Result, t2.Result, t3.Result, t4.Result, t5.Result, t6.Result); 62 | } 63 | 64 | /// 65 | /// Returns the result of all tasks awaited in parallel. 66 | /// 67 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 68 | public static async Task> GetAll(Task t1, Task t2, Task t3, Task t4, Task t5, Task t6, Task t7) 69 | { 70 | await Task.WhenAll(t1, t2, t3, t4, t5, t6, t7).ConfigureAwait(false); 71 | return (t1.Result, t2.Result, t3.Result, t4.Result, t5.Result, t6.Result, t7.Result); 72 | } 73 | 74 | #endregion 75 | } 76 | #endif -------------------------------------------------------------------------------- /Utils/Strings/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Impworks.Utils.Strings; 4 | 5 | public static partial class StringExtensions 6 | { 7 | /// 8 | /// Returns null if the string is null or empty. 9 | /// 10 | /// Checked string. 11 | /// Flag indicating that strings with whitespace chars are considered non-empty. 12 | /// Null if the string is empty/whitespaced, otherwise the string itself. 13 | public static string ValueOrNull(this string str, bool allowWhitespace = false) 14 | { 15 | var check = allowWhitespace 16 | ? (Func) string.IsNullOrEmpty 17 | : string.IsNullOrWhiteSpace; 18 | 19 | return check(str) ? null : str; 20 | } 21 | 22 | /// 23 | /// Checks if the two strings start with the same sequence. 24 | /// 25 | /// First string. 26 | /// Second string. 27 | /// Number of characters to check. 28 | /// Flag for case-insensitive comparison. 29 | public static bool StartsWithPart(this string first, string second, int comparisonLength, bool ignoreCase = false) 30 | { 31 | if (comparisonLength < 1) 32 | throw new ArgumentException("Comparison length must be at least 1", nameof(comparisonLength)); 33 | 34 | if (first == null && second == null) 35 | return true; 36 | 37 | if (first == null || second == null) 38 | return false; 39 | 40 | if(comparisonLength > first.Length || comparisonLength > second.Length) 41 | return string.Compare(first, second, ignoreCase) == 0; 42 | 43 | var cmp = string.Compare( 44 | first, 45 | 0, 46 | second, 47 | 0, 48 | comparisonLength, 49 | ignoreCase 50 | ); 51 | 52 | return cmp == 0; 53 | } 54 | 55 | /// 56 | /// Checks if the two strings end with the same sequence. 57 | /// 58 | /// First string. 59 | /// Second string. 60 | /// Number of characters to check. 61 | /// Flag for case-insensitive comparison. 62 | public static bool EndsWithPart(this string first, string second, int comparisonLength, bool ignoreCase = false) 63 | { 64 | if(comparisonLength < 1) 65 | throw new ArgumentException("Comparison length must be at least 1", nameof(comparisonLength)); 66 | 67 | if(first == null && second == null) 68 | return true; 69 | 70 | if(first == null || second == null) 71 | return false; 72 | 73 | if(comparisonLength > first.Length || comparisonLength > second.Length) 74 | return string.Compare(first, second, ignoreCase) == 0; 75 | 76 | var cmp = string.Compare( 77 | first, 78 | first.Length - comparisonLength, 79 | second, 80 | second.Length - comparisonLength, 81 | comparisonLength, 82 | ignoreCase 83 | ); 84 | 85 | return cmp == 0; 86 | } 87 | } -------------------------------------------------------------------------------- /Utils/Linq/EnumerableExtensions.Partition.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace Impworks.Utils.Linq; 6 | 7 | /// 8 | /// Helper methods for IEnumerable. 9 | /// 10 | public static partial class EnumerableExtensions 11 | { 12 | /// 13 | /// Splits a flat sequence into a sequence of chunks of desired size. 14 | /// 15 | /// 16 | /// new [] { 1, 2, 3, 4, 5, 6 }.PartitionBySize(2) => [1, 2], [3, 4], [5, 6] 17 | /// new [] { 1, 2, 3, 4, 5, 6 }.PartitionBySize(4) => [1, 2, 3, 4], [5, 6] 18 | /// new [] { 1 }.PartitionBySize(3) => [1] 19 | /// 20 | /// Original sequence of values. 21 | /// Maximum number of elements per chunk. 22 | public static IEnumerable> PartitionBySize(this IEnumerable source, int chunkSize) 23 | { 24 | if (source == null) 25 | throw new ArgumentNullException(nameof(source)); 26 | 27 | if (chunkSize < 1) 28 | throw new ArgumentException("Size of the chunk must be at least 1!"); 29 | 30 | return Iterator(); 31 | 32 | IEnumerable> Iterator() 33 | { 34 | var batch = new List(chunkSize); 35 | 36 | foreach (var item in source) 37 | { 38 | batch.Add(item); 39 | 40 | if (batch.Count == chunkSize) 41 | { 42 | yield return batch; 43 | batch = new List(chunkSize); 44 | } 45 | } 46 | 47 | if (batch.Count > 0) 48 | yield return batch; 49 | } 50 | } 51 | 52 | /// 53 | /// Splits the sequence into given number of roughly-equal sub-sequences 54 | /// 55 | /// 56 | /// new [] { 1, 2, 3, 4, 5, 6 }.PartitionByCount(2) => [1, 2, 3], [4, 5, 6] 57 | /// new [] { 1, 2, 3, 4, 5, 6 }.PartitionByCount(3) => [1, 2], [3, 4], [5, 6] 58 | /// new [] { 1, 2, 3, 4, 5 }.PartitionByCount(2) => [1, 2, 3], [4, 5] 59 | /// new [] { 1 }.PartitionByCount(2) => [1] 60 | /// 61 | /// Original sequence of values. 62 | /// Desired number of partitions. 63 | public static List> PartitionByCount(this IEnumerable source, int partsCount) 64 | { 65 | if (source == null) 66 | throw new ArgumentNullException(nameof(source)); 67 | 68 | if (partsCount < 1) 69 | throw new ArgumentException("Size of the chunk must be at least 1!"); 70 | 71 | var sourceList = source.ToList(); 72 | var sublistLength = (double)sourceList.Count / partsCount; 73 | 74 | var result = new List>(partsCount); 75 | var partition = new List(); 76 | var accum = 0.0; 77 | 78 | foreach (var item in sourceList) 79 | { 80 | partition.Add(item); 81 | if (partition.Count + accum >= sublistLength) 82 | { 83 | result.Add(partition); 84 | accum = partition.Count + accum - sublistLength; 85 | partition = new List(); 86 | } 87 | } 88 | 89 | if (partition.Count > 0) 90 | result.Add(partition); 91 | 92 | return result; 93 | } 94 | } -------------------------------------------------------------------------------- /Utils.Tests/Xml/XmlExtensions_All.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Xml.Linq; 3 | using Impworks.Utils.Xml; 4 | using NUnit.Framework; 5 | 6 | namespace Utils.Tests.Xml 7 | { 8 | /// 9 | /// Tests for XmlExtensions methods. 10 | /// 11 | [TestFixture] 12 | public class XmlExtensions_All 13 | { 14 | public XElement GetXml() 15 | { 16 | return new XElement( 17 | "A", 18 | new XAttribute("B", 123), 19 | new XAttribute("C", "@123") 20 | ); 21 | } 22 | 23 | [Test] 24 | public void Attr_returns_attribute_value() 25 | { 26 | Assert.That(GetXml().Attr("B"), Is.EqualTo("123")); 27 | } 28 | 29 | [Test] 30 | public void Attr_returns_null_for_missing_attribute() 31 | { 32 | Assert.IsNull(GetXml().Attr("X")); 33 | } 34 | 35 | [Test] 36 | public void Attr_throws_on_null_xml() 37 | { 38 | Assert.Throws(() => (null as XElement).Attr("A")); 39 | } 40 | 41 | [Test] 42 | public void Attr_throws_on_null_attribute_name() 43 | { 44 | Assert.Throws(() => GetXml().Attr(null)); 45 | } 46 | 47 | [Test] 48 | public void ParseAttr_returns_parsed_attribute_value() 49 | { 50 | Assert.That(GetXml().ParseAttr("B"), Is.EqualTo(123)); 51 | } 52 | 53 | [Test] 54 | public void ParseAttr_uses_parseFunc() 55 | { 56 | Assert.That(GetXml().ParseAttr("B", x => int.Parse(x.TrimStart('@'))), Is.EqualTo(123)); 57 | } 58 | 59 | [Test] 60 | public void ParseAttr_throws_on_missing_attribute() 61 | { 62 | Assert.Throws(() => GetXml().ParseAttr("X")); 63 | } 64 | 65 | [Test] 66 | public void ParseAttr_throws_on_null_xml() 67 | { 68 | Assert.Throws(() => (null as XElement).ParseAttr("A")); 69 | } 70 | 71 | [Test] 72 | public void ParseAttr_throws_on_null_attribute_name() 73 | { 74 | Assert.Throws(() => GetXml().ParseAttr(null)); 75 | } 76 | 77 | [Test] 78 | public void TryParseAttr_returns_parsed_attribute_value() 79 | { 80 | Assert.That(GetXml().TryParseAttr("B"), Is.EqualTo(123)); 81 | } 82 | 83 | [Test] 84 | public void TryParseAttr_returns_null_on_missing_attribute() 85 | { 86 | Assert.IsNull(GetXml().TryParseAttr("X")); 87 | } 88 | 89 | [Test] 90 | public void TryParseAttr_uses_parseFunc() 91 | { 92 | Assert.That(GetXml().TryParseAttr("B", x => int.Parse(x.TrimStart('@'))), Is.EqualTo(123)); 93 | } 94 | 95 | [Test] 96 | public void TryParseAttr_throws_on_null_xml() 97 | { 98 | Assert.Throws(() => (null as XElement).TryParseAttr("A")); 99 | } 100 | 101 | [Test] 102 | public void TryParseAttr_throws_on_null_attribute_name() 103 | { 104 | Assert.Throws(() => GetXml().TryParseAttr(null)); 105 | } 106 | } 107 | } -------------------------------------------------------------------------------- /Utils/Url/UrlHelper.Query.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Runtime.CompilerServices; 6 | using Impworks.Utils.Format; 7 | using Impworks.Utils.Linq; 8 | 9 | namespace Impworks.Utils.Url; 10 | 11 | public static partial class UrlHelper 12 | { 13 | #region Public methods 14 | 15 | /// 16 | /// Converts the object's properties to a query string. 17 | /// 18 | /// Object to deconstruct. 19 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 20 | public static string GetQuery(object obj) 21 | { 22 | if(obj == null) 23 | throw new ArgumentNullException(); 24 | 25 | var props = GetObjectProperties(obj); 26 | return GetQuery(props); 27 | } 28 | 29 | /// 30 | /// Renders the list of properties to a string. 31 | /// 32 | /// List of properties and their values. 33 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 34 | public static string GetQuery(IDictionary props) 35 | { 36 | return GetQuery(props as IEnumerable>); 37 | } 38 | 39 | /// 40 | /// Renders the list of properties to a string. 41 | /// 42 | /// List of properties and their values. 43 | public static string GetQuery(IEnumerable> props) 44 | { 45 | if(props == null) 46 | throw new ArgumentNullException(nameof(props)); 47 | 48 | return props.Select(x => RenderProperty(x.Key, x.Value)) 49 | .Where(x => x != null) 50 | .JoinString("&"); 51 | } 52 | 53 | #endregion 54 | 55 | #region Private helpers 56 | 57 | /// 58 | /// Returns the list of properties in an object. 59 | /// 60 | /// Object to deconstruct. 61 | private static IEnumerable> GetObjectProperties(object obj) 62 | { 63 | if (obj == null) 64 | yield break; 65 | 66 | foreach (var prop in obj.GetType().GetProperties()) 67 | { 68 | if (prop.GetIndexParameters().Length > 0) 69 | continue; 70 | 71 | var value = prop.GetValue(obj); 72 | if (value == null) 73 | continue; 74 | 75 | if (value is IEnumerable enumValue and not string) 76 | { 77 | foreach(var elem in enumValue) 78 | yield return new KeyValuePair(prop.Name, elem); 79 | } 80 | else 81 | { 82 | yield return new KeyValuePair(prop.Name, value); 83 | } 84 | } 85 | } 86 | 87 | /// 88 | /// Renders a single property from the query. 89 | /// 90 | private static string RenderProperty(string propName, object value) 91 | { 92 | if (value == null) 93 | return null; 94 | 95 | var strValue = value is IConvertible cvt 96 | ? cvt.ToInvariantString() 97 | : value.ToString(); 98 | 99 | return Uri.EscapeDataString(propName) + "=" + Uri.EscapeDataString(strValue); 100 | } 101 | 102 | #endregion 103 | } -------------------------------------------------------------------------------- /Utils.Tests/Strings/StringHelper_Transliterate.cs: -------------------------------------------------------------------------------- 1 | using System.Text.RegularExpressions; 2 | using Impworks.Utils.Strings; 3 | using NUnit.Framework; 4 | 5 | namespace Utils.Tests.Strings 6 | { 7 | /// 8 | /// Tests for StringHelper.Transliterate. 9 | /// 10 | [TestFixture] 11 | public class StringHelper_Transliterate 12 | { 13 | [Test] 14 | public void Transliterate_transliterates_russian_characters() 15 | { 16 | var src = "абвгдеёжзийклмнопрстуфхцчшщъыьэюя"; 17 | var result = StringHelper.Transliterate(src); 18 | 19 | Assert.IsTrue(Regex.IsMatch(src, "[а-я]")); 20 | Assert.IsFalse(Regex.IsMatch(result, "[а-я]")); 21 | } 22 | 23 | [Test] 24 | public void Transliterate_converts_uppercase_chars() 25 | { 26 | var src = "ПРИВЕТ"; 27 | Assert.That(StringHelper.Transliterate(src), Is.EqualTo("PRIVET")); 28 | } 29 | 30 | [Test] 31 | public void Transliterate_converts_long_uppercase_chars_by_capitalization() 32 | { 33 | var src = "Шалом"; 34 | Assert.That(StringHelper.Transliterate(src), Is.EqualTo("Shalom")); 35 | } 36 | 37 | [Test] 38 | public void Transliterate_ignores_english_characters() 39 | { 40 | var src = "abcdefghijklmnopqrstuvwxyz"; 41 | 42 | Assert.That(StringHelper.Transliterate(src), Is.EqualTo(src)); 43 | } 44 | 45 | [Test] 46 | public void Transliterate_ignores_digits_and_few_special_chars() 47 | { 48 | var src = "1234567890-_."; 49 | 50 | Assert.That(StringHelper.Transliterate(src), Is.EqualTo(src)); 51 | } 52 | 53 | [Test] 54 | public void Transliterate_replaces_other_special_chars_to_fallback_char() 55 | { 56 | var src = "hello, world!"; 57 | 58 | Assert.That(StringHelper.Transliterate(src), Is.EqualTo("hello-world")); 59 | } 60 | 61 | [Test] 62 | public void Transliterate_leaves_duplicates_fallback_char_in_middle_if_cleanOutput_is_false() 63 | { 64 | var src = "hello, world"; 65 | 66 | Assert.That(StringHelper.Transliterate(src, cleanOutput: false), Is.EqualTo("hello--world")); 67 | } 68 | 69 | [Test] 70 | public void Transliterate_leaves_trailing_fallback_char_if_cleanOutput_is_false() 71 | { 72 | var src = "hello!"; 73 | 74 | Assert.That(StringHelper.Transliterate(src, cleanOutput: false), Is.EqualTo("hello-")); 75 | } 76 | 77 | [Test] 78 | public void Transliterate_leaves_leading_fallback_char_if_cleanOutput_is_false() 79 | { 80 | var src = "~hello"; 81 | 82 | Assert.That(StringHelper.Transliterate(src, cleanOutput: false), Is.EqualTo("-hello")); 83 | } 84 | 85 | [Test] 86 | public void Transliterate_uses_given_fallbackChar() 87 | { 88 | var src = "hello there, world"; 89 | 90 | Assert.That(StringHelper.Transliterate(src, fallbackChar: "@@"), Is.EqualTo("hello@@there@@world")); 91 | } 92 | 93 | [Test] 94 | public void Transliterate_uses_safe_regex() 95 | { 96 | var src = "hello there, world"; 97 | 98 | Assert.That(StringHelper.Transliterate(src, safeRegex: @"[a-z\s]"), Is.EqualTo("hello there- world")); 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /Utils.Tests/Dictionary/DictionaryHelper_TryGetValue.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using System.Collections.Generic; 3 | using Impworks.Utils.Dictionary; 4 | 5 | namespace Utils.Tests.Dictionary 6 | { 7 | /// 8 | /// Tests for DictionaryHelper.TryGetValue. 9 | /// 10 | [TestFixture] 11 | public class DictionaryHelper_TryGetValue 12 | { 13 | [Test] 14 | public void TryGetValue_returns_value_for_existing_keys() 15 | { 16 | var dict = new Dictionary 17 | { 18 | ["hello"] = "world", 19 | ["a"] = "b" 20 | }; 21 | 22 | Assert.That(dict.TryGetValue("hello"), Is.EqualTo("world")); 23 | } 24 | 25 | [Test] 26 | public void TryGetValue_returns_null_for_missing_ref_types() 27 | { 28 | var dict = new Dictionary 29 | { 30 | ["hello"] = "world", 31 | ["a"] = "b" 32 | }; 33 | 34 | Assert.IsNull(dict.TryGetValue("bla")); 35 | } 36 | 37 | [Test] 38 | public void TryGetValue_returns_default_for_missing_value_types() 39 | { 40 | var dict = new Dictionary 41 | { 42 | ["hello"] = 1, 43 | ["a"] = 2 44 | }; 45 | 46 | Assert.That(dict.TryGetValue("bla"), Is.EqualTo(0)); 47 | } 48 | 49 | [Test] 50 | public void TryGetValue_accepts_list() 51 | { 52 | var dict = new Dictionary 53 | { 54 | [1] = 13, 55 | [2] = 37 56 | }; 57 | 58 | Assert.That(dict.TryGetValue(3, 2, 1), Is.EqualTo(37)); 59 | } 60 | 61 | [Test] 62 | public void TryGetValue_with_list_returns_default_for_missing_value_types() 63 | { 64 | var dict = new Dictionary 65 | { 66 | [1] = 13, 67 | [2] = 37 68 | }; 69 | 70 | Assert.That(dict.TryGetValue(4, 5), Is.EqualTo(0)); 71 | } 72 | 73 | [Test] 74 | public void TryGetValue_with_list_returns_null_for_missing_ref_types() 75 | { 76 | var dict = new Dictionary 77 | { 78 | [1] = "foo", 79 | [2] = "bar" 80 | }; 81 | 82 | Assert.That(dict.TryGetValue(4, 5), Is.Null); 83 | } 84 | 85 | [Test] 86 | public void TryGetValue_returns_default_on_null_key_as_single_elem() 87 | { 88 | var dict = new Dictionary 89 | { 90 | ["a"] = "foo", 91 | ["b"] = "bar" 92 | }; 93 | 94 | Assert.That(dict.TryGetValue(null as string), Is.Null); 95 | } 96 | 97 | [Test] 98 | public void TryGetValue_returns_default_on_null_key_as_array() 99 | { 100 | var dict = new Dictionary 101 | { 102 | ["a"] = "foo", 103 | ["b"] = "bar" 104 | }; 105 | 106 | Assert.That(dict.TryGetValue(null as string[]), Is.Null); 107 | } 108 | 109 | [Test] 110 | public void TryGetValue_skips_nulls_in_key_list() 111 | { 112 | var dict = new Dictionary 113 | { 114 | ["a"] = "foo", 115 | ["b"] = "bar" 116 | }; 117 | 118 | Assert.That(dict.TryGetValue(null, null, "a"), Is.EqualTo("foo")); 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /Utils/Exceptions/Try.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace Impworks.Utils.Exceptions; 5 | 6 | /// 7 | /// Helper methods to ignore exceptions fluently. 8 | /// 9 | public class Try 10 | { 11 | #region Sync 12 | 13 | /// 14 | /// Invokes the action, ignoring all exceptions. 15 | /// 16 | public static void Do(Action act) 17 | { 18 | try 19 | { 20 | act(); 21 | } 22 | catch 23 | { 24 | // ignored 25 | } 26 | } 27 | 28 | /// 29 | /// Returns the value of the function, or the default value in case of an exception. 30 | /// 31 | public static T Get(Func generator) 32 | { 33 | try 34 | { 35 | return generator(); 36 | } 37 | catch 38 | { 39 | return default; 40 | } 41 | } 42 | 43 | /// 44 | /// Returns the value of the function, or the fallback value in case of an exception. 45 | /// 46 | public static T Get(Func generator, T fallback) 47 | { 48 | try 49 | { 50 | return generator(); 51 | } 52 | catch 53 | { 54 | return fallback; 55 | } 56 | } 57 | 58 | /// 59 | /// Returns the value of the first function that does not throw an exception, or the default value. 60 | /// 61 | public static T Get(params Func[] generators) 62 | { 63 | foreach (var generator in generators) 64 | { 65 | try 66 | { 67 | return generator(); 68 | } 69 | catch 70 | { 71 | // ignore 72 | } 73 | } 74 | 75 | return default; 76 | } 77 | 78 | #endregion 79 | 80 | #region Async 81 | 82 | /// 83 | /// Invokes the action, ignoring all exceptions. 84 | /// 85 | public static async Task DoAsync(Func act) 86 | { 87 | try 88 | { 89 | await act().ConfigureAwait(false); 90 | } 91 | catch 92 | { 93 | // ignored 94 | } 95 | } 96 | 97 | /// 98 | /// Returns the value of the task, or the default value in case of an exception. 99 | /// 100 | public static async Task GetAsync(Func> generator) 101 | { 102 | try 103 | { 104 | return await generator().ConfigureAwait(false); 105 | } 106 | catch 107 | { 108 | return default; 109 | } 110 | } 111 | 112 | /// 113 | /// Returns the value of the task, or the fallback value in case of an exception. 114 | /// 115 | public static async Task GetAsync(Func> generator, T fallback) 116 | { 117 | try 118 | { 119 | return await generator().ConfigureAwait(false); 120 | } 121 | catch 122 | { 123 | return fallback; 124 | } 125 | } 126 | 127 | /// 128 | /// Returns the value of the first task that does not throw an exception, or the default value. 129 | /// 130 | public static async Task GetAsync(params Func>[] generators) 131 | { 132 | foreach (var generator in generators) 133 | { 134 | try 135 | { 136 | return await generator().ConfigureAwait(false); 137 | } 138 | catch 139 | { 140 | // ignore 141 | } 142 | } 143 | 144 | return default; 145 | } 146 | 147 | #endregion 148 | } -------------------------------------------------------------------------------- /Utils.Tests/Linq/EnumerableExtensions_FirstN.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Impworks.Utils.Linq; 3 | using NUnit.Framework; 4 | 5 | namespace Utils.Tests.Linq 6 | { 7 | /// 8 | /// Tests for EnumerableExtensions.FirstN. 9 | /// 10 | [TestFixture] 11 | public class EnumerableExtensions_FirstN 12 | { 13 | [Test] 14 | public void FirstN_throws_for_smaller_arrays() 15 | { 16 | Assert.Throws(() => new[] {1}.First2(), "Sequence contains too few elements"); 17 | Assert.Throws(() => new[] {1}.First3(), "Sequence contains too few elements"); 18 | Assert.Throws(() => new[] {1}.First4(), "Sequence contains too few elements"); 19 | Assert.Throws(() => new[] {1}.First5(), "Sequence contains too few elements"); 20 | Assert.Throws(() => new[] {1}.First6(), "Sequence contains too few elements"); 21 | Assert.Throws(() => new[] {1}.First7(), "Sequence contains too few elements"); 22 | } 23 | 24 | [Test] 25 | [TestCase(new [] { 1, 2 })] 26 | [TestCase(new [] { 1, 2, 3 })] 27 | public void First2_returns_two_values(int[] array) 28 | { 29 | var (a, b) = array.First2(); 30 | Assert.That(a, Is.EqualTo(1)); 31 | Assert.That(b, Is.EqualTo(2)); 32 | } 33 | 34 | [Test] 35 | [TestCase(new [] { 1, 2, 3 })] 36 | [TestCase(new [] { 1, 2, 3, 4 })] 37 | public void First3_returns_three_values(int[] array) 38 | { 39 | var (a, b, c) = array.First3(); 40 | Assert.That(a, Is.EqualTo(1)); 41 | Assert.That(b, Is.EqualTo(2)); 42 | Assert.That(c, Is.EqualTo(3)); 43 | } 44 | 45 | [Test] 46 | [TestCase(new [] { 1, 2, 3, 4 })] 47 | [TestCase(new [] { 1, 2, 3, 4, 5 })] 48 | public void First4_returns_four_values(int[] array) 49 | { 50 | var (a, b, c, d) = array.First4(); 51 | Assert.That(a, Is.EqualTo(1)); 52 | Assert.That(b, Is.EqualTo(2)); 53 | Assert.That(c, Is.EqualTo(3)); 54 | Assert.That(d, Is.EqualTo(4)); 55 | } 56 | 57 | [Test] 58 | [TestCase(new [] { 1, 2, 3, 4, 5 })] 59 | [TestCase(new [] { 1, 2, 3, 4, 5, 6 })] 60 | public void First5_returns_five_values(int[] array) 61 | { 62 | var (a, b, c, d, e) = array.First5(); 63 | Assert.That(a, Is.EqualTo(1)); 64 | Assert.That(b, Is.EqualTo(2)); 65 | Assert.That(c, Is.EqualTo(3)); 66 | Assert.That(d, Is.EqualTo(4)); 67 | Assert.That(e, Is.EqualTo(5)); 68 | } 69 | 70 | [Test] 71 | [TestCase(new [] { 1, 2, 3, 4, 5, 6 })] 72 | [TestCase(new [] { 1, 2, 3, 4, 5, 6, 7 })] 73 | public void First6_returns_six_values(int[] array) 74 | { 75 | var (a, b, c, d, e, f) = array.First6(); 76 | Assert.That(a, Is.EqualTo(1)); 77 | Assert.That(b, Is.EqualTo(2)); 78 | Assert.That(c, Is.EqualTo(3)); 79 | Assert.That(d, Is.EqualTo(4)); 80 | Assert.That(e, Is.EqualTo(5)); 81 | Assert.That(f, Is.EqualTo(6)); 82 | } 83 | 84 | [Test] 85 | [TestCase(new [] { 1, 2, 3, 4, 5, 6, 7 })] 86 | [TestCase(new [] { 1, 2, 3, 4, 5, 6, 7, 8 })] 87 | public void First7_returns_seven_values(int[] array) 88 | { 89 | var (a, b, c, d, e, f, g) = array.First7(); 90 | Assert.That(a, Is.EqualTo(1)); 91 | Assert.That(b, Is.EqualTo(2)); 92 | Assert.That(c, Is.EqualTo(3)); 93 | Assert.That(d, Is.EqualTo(4)); 94 | Assert.That(e, Is.EqualTo(5)); 95 | Assert.That(f, Is.EqualTo(6)); 96 | Assert.That(g, Is.EqualTo(7)); 97 | } 98 | } 99 | } -------------------------------------------------------------------------------- /Utils/Strings/StringHelper.Transliterate.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text; 3 | using System.Text.RegularExpressions; 4 | 5 | namespace Impworks.Utils.Strings; 6 | 7 | public static partial class StringHelper 8 | { 9 | /// 10 | /// Russian to english character mapping. 11 | /// 12 | private static readonly Dictionary _charMap = new Dictionary 13 | { 14 | ['а'] = "a", 15 | ['б'] = "b", 16 | ['в'] = "v", 17 | ['г'] = "g", 18 | ['д'] = "d", 19 | ['е'] = "e", 20 | ['ё'] = "yo", 21 | ['ж'] = "zh", 22 | ['з'] = "z", 23 | ['и'] = "i", 24 | ['й'] = "j", 25 | ['к'] = "k", 26 | ['л'] = "l", 27 | ['м'] = "m", 28 | ['н'] = "n", 29 | ['о'] = "o", 30 | ['п'] = "p", 31 | ['р'] = "r", 32 | ['с'] = "s", 33 | ['т'] = "t", 34 | ['у'] = "u", 35 | ['ф'] = "f", 36 | ['х'] = "kh", 37 | ['ц'] = "ts", 38 | ['ч'] = "ch", 39 | ['ш'] = "sh", 40 | ['щ'] = "sch", 41 | ['ъ'] = "'", 42 | ['ы'] = "y", 43 | ['ь'] = "'", 44 | ['э'] = "e", 45 | ['ю'] = "yu", 46 | ['я'] = "ya" 47 | }; 48 | 49 | /// 50 | /// Characters that must not be transliterated. 51 | /// 52 | private static readonly Regex _safeChecker = new Regex(@"[a-z0-9_\.-]", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.ExplicitCapture); 53 | 54 | /// 55 | /// Transliterates a string, replacing russian characters with english ones. 56 | /// 57 | /// String to transliterate. 58 | /// Character to use instead of unknown locale chars. Defaults to dash. 59 | /// Regular expression to check characters that must be left as-is. 60 | /// Flag indicating that fallback characters will be cleaned on start/end and not duplicated. 61 | public static string Transliterate(string str, string fallbackChar = "-", string safeRegex = null, bool cleanOutput = true) 62 | { 63 | var sb = new StringBuilder(str.Length); 64 | var safeChecker = safeRegex == null 65 | ? _safeChecker 66 | : new Regex(safeRegex, RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture); 67 | var lastAddedFallback = false; 68 | 69 | foreach (var ch in str) 70 | { 71 | // character is russian 72 | if (_charMap.TryGetValue(ch, out var rep)) 73 | { 74 | sb.Append(rep); 75 | lastAddedFallback = false; 76 | continue; 77 | } 78 | 79 | var chstr = ch.ToString(); 80 | 81 | // character is russian, but uppercase: capitalize it 82 | if (_charMap.TryGetValue(chstr.ToLowerInvariant()[0], out var repUpper)) 83 | { 84 | sb.Append(repUpper.Capitalize()); 85 | lastAddedFallback = false; 86 | continue; 87 | } 88 | 89 | // character is safe: append as is 90 | if (safeChecker.IsMatch(chstr)) 91 | { 92 | sb.Append(ch); 93 | lastAddedFallback = false; 94 | continue; 95 | } 96 | 97 | // fallback 98 | if (!lastAddedFallback || !cleanOutput) 99 | { 100 | sb.Append(fallbackChar); 101 | lastAddedFallback = true; 102 | } 103 | } 104 | 105 | var result = sb.ToString(); 106 | 107 | if (cleanOutput) 108 | { 109 | var esc = Regex.Escape(fallbackChar); 110 | result = Regex.Replace(result, $"^{esc}|{esc}$", ""); 111 | } 112 | 113 | return result; 114 | } 115 | } -------------------------------------------------------------------------------- /Utils.Tests/Xml/XmlHelper_Serialize.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | using Impworks.Utils.Xml; 3 | using NUnit.Framework; 4 | using Utils.Tests.Linq; 5 | 6 | namespace Utils.Tests.Xml 7 | { 8 | /// 9 | /// Tests for XmlHelper.Serialize. 10 | /// 11 | [TestFixture] 12 | public class XmlHelper_Serialize 13 | { 14 | SampleObject GetObj(bool useNull = false) 15 | { 16 | return new SampleObject(1, useNull ? null : "hello"); 17 | } 18 | 19 | [Test] 20 | public void Serialize_produces_valid_xml() 21 | { 22 | var xml = XmlHelper.Serialize(GetObj()); 23 | Assert.DoesNotThrow(() => XElement.Parse(xml)); 24 | } 25 | 26 | #region Clean mode 27 | 28 | [Test] 29 | public void Serialize_removes_xml_header_in_clean_mode() 30 | { 31 | var xml = XmlHelper.Serialize(GetObj()); 32 | Assert.IsTrue(xml.StartsWith(" 9 | /// Tests for TaskHelper.GetAll. 10 | /// 11 | [TestFixture] 12 | public class TaskHelper_GetAll 13 | { 14 | [Test] 15 | public async Task GetAll_returns_2_values() 16 | { 17 | var (i1, i2) = await TaskHelper.GetAll( 18 | Task.FromResult(1), 19 | Task.FromResult(2) 20 | ); 21 | 22 | Assert.That(i1, Is.EqualTo(1)); 23 | Assert.That(i2, Is.EqualTo(2)); 24 | } 25 | 26 | [Test] 27 | public async Task GetAll_returns_3_values() 28 | { 29 | var (i1, i2, i3) = await TaskHelper.GetAll( 30 | Task.FromResult(1), 31 | Task.FromResult(2), 32 | Task.FromResult(3) 33 | ); 34 | 35 | Assert.That(i1, Is.EqualTo(1)); 36 | Assert.That(i2, Is.EqualTo(2)); 37 | Assert.That(i3, Is.EqualTo(3)); 38 | } 39 | 40 | [Test] 41 | public async Task GetAll_returns_4_values() 42 | { 43 | var (i1, i2, i3, i4) = await TaskHelper.GetAll( 44 | Task.FromResult(1), 45 | Task.FromResult(2), 46 | Task.FromResult(3), 47 | Task.FromResult(4) 48 | ); 49 | 50 | Assert.That(i1, Is.EqualTo(1)); 51 | Assert.That(i2, Is.EqualTo(2)); 52 | Assert.That(i3, Is.EqualTo(3)); 53 | Assert.That(i4, Is.EqualTo(4)); 54 | } 55 | 56 | [Test] 57 | public async Task GetAll_returns_5_values() 58 | { 59 | var (i1, i2, i3, i4, i5) = await TaskHelper.GetAll( 60 | Task.FromResult(1), 61 | Task.FromResult(2), 62 | Task.FromResult(3), 63 | Task.FromResult(4), 64 | Task.FromResult(5) 65 | ); 66 | 67 | Assert.That(i1, Is.EqualTo(1)); 68 | Assert.That(i2, Is.EqualTo(2)); 69 | Assert.That(i3, Is.EqualTo(3)); 70 | Assert.That(i4, Is.EqualTo(4)); 71 | Assert.That(i5, Is.EqualTo(5)); 72 | } 73 | 74 | [Test] 75 | public async Task GetAll_returns_6_values() 76 | { 77 | var (i1, i2, i3, i4, i5, i6) = await TaskHelper.GetAll( 78 | Task.FromResult(1), 79 | Task.FromResult(2), 80 | Task.FromResult(3), 81 | Task.FromResult(4), 82 | Task.FromResult(5), 83 | Task.FromResult(6) 84 | ); 85 | 86 | Assert.That(i1, Is.EqualTo(1)); 87 | Assert.That(i2, Is.EqualTo(2)); 88 | Assert.That(i3, Is.EqualTo(3)); 89 | Assert.That(i4, Is.EqualTo(4)); 90 | Assert.That(i5, Is.EqualTo(5)); 91 | Assert.That(i6, Is.EqualTo(6)); 92 | } 93 | 94 | [Test] 95 | public async Task GetAll_returns_7_values() 96 | { 97 | var (i1, i2, i3, i4, i5, i6, i7) = await TaskHelper.GetAll( 98 | Task.FromResult(1), 99 | Task.FromResult(2), 100 | Task.FromResult(3), 101 | Task.FromResult(4), 102 | Task.FromResult(5), 103 | Task.FromResult(6), 104 | Task.FromResult(7) 105 | ); 106 | 107 | Assert.That(i1, Is.EqualTo(1)); 108 | Assert.That(i2, Is.EqualTo(2)); 109 | Assert.That(i3, Is.EqualTo(3)); 110 | Assert.That(i4, Is.EqualTo(4)); 111 | Assert.That(i5, Is.EqualTo(5)); 112 | Assert.That(i6, Is.EqualTo(6)); 113 | Assert.That(i7, Is.EqualTo(7)); 114 | } 115 | 116 | [Test] 117 | public void GetAll_throws_exception_if_task_failed() 118 | { 119 | Assert.ThrowsAsync(async () => 120 | { 121 | await TaskHelper.GetAll( 122 | Task.FromResult(1), 123 | Task.FromException(new NotImplementedException()) 124 | ); 125 | }); 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /Utils/Format/EnumHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.ComponentModel; 5 | using System.Linq; 6 | using System.Reflection; 7 | using System.Runtime.CompilerServices; 8 | 9 | namespace Impworks.Utils.Format; 10 | 11 | /// 12 | /// Various extension / helper methods for enum values. 13 | /// 14 | public static class EnumHelper 15 | { 16 | /// 17 | /// Cached description values for all values of known enums. 18 | /// 19 | private static readonly ConcurrentDictionary DescriptionCache = new ConcurrentDictionary(); 20 | 21 | /// 22 | /// Cached description values for all values of known enums. 23 | /// 24 | private static readonly ConcurrentDictionary DynamicDescriptionCache = new ConcurrentDictionary(); 25 | 26 | #region Strongly typed 27 | 28 | /// 29 | /// Returns the lookup of enum values and readable descriptions (from [Description] attribute or the label). 30 | /// 31 | public static IReadOnlyDictionary GetEnumDescriptions() 32 | where T : struct 33 | { 34 | var type = typeof(T); 35 | var lookup = DescriptionCache.GetOrAdd(type, t => 36 | { 37 | var flags = BindingFlags.DeclaredOnly 38 | | BindingFlags.Static 39 | | BindingFlags.Public 40 | | BindingFlags.GetField; 41 | 42 | return type.GetFields(flags) 43 | .ToDictionary( 44 | x => (T)x.GetRawConstantValue(), 45 | x => x.GetCustomAttribute()?.Description 46 | ?? Enum.GetName(type, x.GetRawConstantValue()) 47 | ); 48 | }); 49 | 50 | return (IReadOnlyDictionary)lookup; 51 | } 52 | 53 | /// 54 | /// Returns a readable description of a single enum value. 55 | /// 56 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 57 | public static string GetEnumDescription(this T enumValue) 58 | where T : struct 59 | { 60 | return GetEnumDescriptions()[enumValue]; 61 | } 62 | 63 | /// 64 | /// Returns the list of enum values. 65 | /// 66 | public static IReadOnlyList GetEnumValues() 67 | where T : struct 68 | { 69 | return Enum.GetValues(typeof(T)) 70 | .Cast() 71 | .ToList(); 72 | } 73 | 74 | /// 75 | /// Checks if the element is defined, possibly ignoring the case. 76 | /// 77 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 78 | public static bool IsDefined(string name, bool ignoreCase = false) 79 | { 80 | return ignoreCase 81 | ? Enum.GetNames(typeof(T)).Any(x => string.Compare(x, name, StringComparison.InvariantCultureIgnoreCase) == 0) 82 | : Enum.IsDefined(typeof(T), name); 83 | } 84 | 85 | #endregion 86 | 87 | #region Weakly typed 88 | 89 | /// 90 | /// Returns the lookup of enum values and readable descriptions (from [Description] attribute or the label). 91 | /// 92 | public static IReadOnlyDictionary GetEnumDescriptions(Type type) 93 | { 94 | if(!type.IsEnum) 95 | throw new ArgumentException($"Type {type.Name} is not an Enum.", nameof(type)); 96 | 97 | var lookup = DynamicDescriptionCache.GetOrAdd(type, t => 98 | { 99 | var flags = BindingFlags.DeclaredOnly 100 | | BindingFlags.Static 101 | | BindingFlags.Public 102 | | BindingFlags.GetField; 103 | 104 | return type.GetFields(flags) 105 | .ToDictionary( 106 | x => Enum.ToObject(type, x.GetRawConstantValue()), 107 | x => x.GetCustomAttribute()?.Description 108 | ?? Enum.GetName(type, x.GetRawConstantValue()) 109 | ); 110 | }); 111 | 112 | return (IReadOnlyDictionary) lookup; 113 | } 114 | 115 | #endregion 116 | } -------------------------------------------------------------------------------- /Utils/Linq/ExprHelper.AndOr.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Runtime.CompilerServices; 6 | 7 | namespace Impworks.Utils.Linq; 8 | 9 | /// 10 | /// Helper methods for working with expressions. 11 | /// 12 | public static partial class ExprHelper 13 | { 14 | #region Disjunction 15 | 16 | /// 17 | /// Returns a predicate that is true if any of the given predicates are true (OR). 18 | /// 19 | /// List of predicates to combine. 20 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 21 | public static Expression> Or(params Expression>[] predicates) 22 | { 23 | return Or(predicates.AsEnumerable()); 24 | } 25 | 26 | /// 27 | /// Returns a predicate that is true if any of the given predicates are true (OR). 28 | /// 29 | /// List of predicates to combine. 30 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 31 | public static Expression> Or(IEnumerable>> predicates) 32 | { 33 | return Combine(predicates, Expression.Or); 34 | } 35 | 36 | #endregion 37 | 38 | #region Conjunction 39 | 40 | /// 41 | /// Returns a predicate that is true if all of the given predicates are true (AND). 42 | /// 43 | /// List of predicates to combine. 44 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 45 | public static Expression> And(params Expression>[] predicates) 46 | { 47 | return And(predicates.AsEnumerable()); 48 | } 49 | 50 | /// 51 | /// Returns a predicate that is true if all of the given predicates are true (AND). 52 | /// 53 | /// List of predicates to combine. 54 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 55 | public static Expression> And(IEnumerable>> predicates) 56 | { 57 | return Combine(predicates, Expression.And); 58 | } 59 | 60 | #endregion 61 | 62 | #region Implementation 63 | 64 | /// 65 | /// Combines expressions using a binary operator. 66 | /// 67 | private static Expression> Combine(IEnumerable>> predicates, Func combinator) 68 | { 69 | if (predicates == null) 70 | throw new ArgumentNullException(nameof(predicates)); 71 | if (combinator == null) 72 | throw new ArgumentNullException(nameof(combinator)); 73 | 74 | var arg = Expression.Parameter(typeof(T), "arg"); 75 | 76 | var curr = null as Expression; 77 | foreach (var pred in predicates) 78 | { 79 | var fixedPred = new ReplaceParameterByParameterVisitor(pred.Parameters.Single(), arg).VisitAndConvert(pred, nameof(Combine)); 80 | curr = curr == null ? fixedPred.Body : combinator(curr, fixedPred.Body); 81 | } 82 | 83 | if(curr == null) 84 | throw new ArgumentException("No predicates have been specified!", nameof(predicates)); 85 | 86 | return Expression.Lambda>(curr, arg); 87 | } 88 | 89 | /// 90 | /// Helper visitor class for replacing one parameter with another. 91 | /// 92 | private class ReplaceParameterByParameterVisitor : ExpressionVisitor 93 | { 94 | public ReplaceParameterByParameterVisitor(ParameterExpression source, ParameterExpression target) 95 | { 96 | _source = source ?? throw new ArgumentNullException(nameof(source)); 97 | _target = target ?? throw new ArgumentNullException(nameof(target)); 98 | } 99 | 100 | private readonly ParameterExpression _source; 101 | private readonly ParameterExpression _target; 102 | 103 | protected override Expression VisitLambda(Expression node) 104 | { 105 | var ps = node.Parameters; 106 | if (ps.Count == 1 && ps[0] == _source) 107 | return Expression.Lambda(Visit(node.Body), _target); 108 | return node; 109 | } 110 | 111 | protected override Expression VisitParameter(ParameterExpression node) => node == _source ? _target : base.VisitParameter(node); 112 | } 113 | 114 | #endregion 115 | } -------------------------------------------------------------------------------- /Utils.Tests/Exceptions/Try_Get.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Impworks.Utils.Exceptions; 4 | using NUnit.Framework; 5 | 6 | namespace Utils.Tests.Exceptions 7 | { 8 | /// 9 | /// Tests for Try.Get and Try.GetAsync. 10 | /// 11 | [TestFixture] 12 | public class Try_Get 13 | { 14 | #region Sync 15 | 16 | [Test] 17 | public void Returns_value_sync() 18 | { 19 | Assert.That(Try.Get(() => 1), Is.EqualTo(1)); 20 | } 21 | 22 | [Test] 23 | public void Returns_default_sync() 24 | { 25 | Assert.That(Try.Get(() => throw new Exception()), Is.EqualTo(0)); 26 | } 27 | 28 | [Test] 29 | public void Returns_fallback_sync() 30 | { 31 | Assert.That(Try.Get(() => throw new Exception(), 2), Is.EqualTo(2)); 32 | } 33 | 34 | [Test] 35 | public void Returns_first_non_throwing_sync() 36 | { 37 | Assert.That( 38 | Try.Get( 39 | () => throw new Exception(), 40 | () => throw new NotImplementedException(), 41 | () => 2, 42 | () => 3 43 | ), 44 | Is.EqualTo(2) 45 | ); 46 | } 47 | 48 | [Test] 49 | public void Returns_default_multiple_sync() 50 | { 51 | Assert.That( 52 | Try.Get( 53 | () => throw new Exception(), 54 | () => throw new NotImplementedException() 55 | ), 56 | Is.EqualTo(0) 57 | ); 58 | } 59 | 60 | #endregion 61 | 62 | #region Async 63 | 64 | [Test] 65 | public async Task Returns_value_async() 66 | { 67 | Assert.That( 68 | await Try.GetAsync(async () => 69 | { 70 | await Task.Yield(); 71 | return 1; 72 | }), 73 | Is.EqualTo(1) 74 | ); 75 | } 76 | 77 | [Test] 78 | public async Task Returns_default_async() 79 | { 80 | Assert.That( 81 | await Try.GetAsync(async () => 82 | { 83 | await Task.Yield(); 84 | throw new Exception(); 85 | }), 86 | Is.EqualTo(0) 87 | ); 88 | } 89 | 90 | [Test] 91 | public async Task Returns_fallback_async() 92 | { 93 | Assert.That( 94 | await Try.GetAsync( 95 | async () => 96 | { 97 | await Task.Yield(); 98 | throw new Exception(); 99 | }, 100 | 2), 101 | Is.EqualTo(2) 102 | ); 103 | } 104 | 105 | [Test] 106 | public async Task Returns_first_non_throwing_async() 107 | { 108 | Assert.That( 109 | await Try.GetAsync( 110 | async () => 111 | { 112 | await Task.Yield(); 113 | throw new Exception(); 114 | }, 115 | async () => 116 | { 117 | await Task.Yield(); 118 | throw new NotImplementedException(); 119 | }, 120 | async () => 121 | { 122 | await Task.Yield(); 123 | return 2; 124 | }, 125 | async () => 126 | { 127 | await Task.Yield(); 128 | return 3; 129 | }), 130 | Is.EqualTo(2) 131 | ); 132 | } 133 | 134 | [Test] 135 | public async Task Returns_default_multiple_async() 136 | { 137 | Assert.That( 138 | await Try.GetAsync( 139 | async () => 140 | { 141 | await Task.Yield(); 142 | throw new Exception(); 143 | }, 144 | async () => 145 | { 146 | await Task.Yield(); 147 | throw new NotImplementedException(); 148 | }), 149 | Is.EqualTo(0) 150 | ); 151 | } 152 | 153 | #endregion 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /Utils.Tests/Linq/EnumerableExtensions_OrderBy_IQueryable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Impworks.Utils.Linq; 4 | using NUnit.Framework; 5 | 6 | namespace Utils.Tests.Linq 7 | { 8 | /// 9 | /// Tests for EnumerableExtensions.OrderBy's IQueryable overrides. 10 | /// 11 | [TestFixture] 12 | public class EnumerableExtensions_OrderBy_IQueryable 13 | { 14 | private IQueryable GetSampleObjects() 15 | { 16 | return new[] 17 | { 18 | new SampleObject(3, "hello"), 19 | new SampleObject(1, "world"), 20 | new SampleObject(2, "abra") 21 | } 22 | .AsQueryable(); 23 | } 24 | 25 | [Test] 26 | public void OrderBy_orders_ascending_when_flag_is_false() 27 | { 28 | var objs = GetSampleObjects(); 29 | 30 | var result = objs.OrderBy(x => x.Value, false).Select(x => x.Str); 31 | 32 | Assert.That(result, Is.EqualTo(new[] {"world", "abra", "hello"})); 33 | } 34 | 35 | [Test] 36 | public void OrderBy_orders_descending_when_flag_is_true() 37 | { 38 | var objs = GetSampleObjects(); 39 | 40 | var result = objs.OrderBy(x => x.Value, true).Select(x => x.Str); 41 | 42 | Assert.That(result, Is.EqualTo(new[] {"hello", "abra", "world"})); 43 | } 44 | 45 | [Test] 46 | public void OrderBy_orders_by_property_name() 47 | { 48 | var objs = GetSampleObjects(); 49 | 50 | var result = objs.OrderBy(nameof(SampleObject.Value), false) 51 | .Select(x => x.Str); 52 | 53 | Assert.That(result, Is.EqualTo(new[] {"world", "abra", "hello"})); 54 | } 55 | 56 | [Test] 57 | public void OrderBy_orders_by_field_name() 58 | { 59 | var objs = new[] 60 | { 61 | new SampleObject { Value = 1, Field = "hello" }, 62 | new SampleObject { Value = 2, Field = "world" }, 63 | new SampleObject { Value = 3, Field = "abra" }, 64 | }; 65 | 66 | var result = objs.AsQueryable() 67 | .OrderBy(nameof(SampleObject.Field), false) 68 | .Select(x => x.Value); 69 | 70 | Assert.That(result, Is.EqualTo(new[] {3, 1, 2})); 71 | } 72 | 73 | [Test] 74 | public void OrderBy_orders_by_property_name_descending_when_flag_is_true() 75 | { 76 | var objs = GetSampleObjects(); 77 | 78 | var result = objs.OrderBy(nameof(SampleObject.Value), true) 79 | .Select(x => x.Str); 80 | 81 | Assert.That(result, Is.EqualTo(new[] {"hello", "abra", "world"})); 82 | } 83 | 84 | [Test] 85 | public void OrderBy_throws_ArgumentException_on_missing_property() 86 | { 87 | Assert.Throws(() => GetSampleObjects().OrderBy("Blabla", true)); 88 | } 89 | 90 | [Test] 91 | public void ThenBy_orders_ascending_when_flag_is_false() 92 | { 93 | var objs = GetSampleObjects(); 94 | 95 | var result = objs.OrderBy(x => x.Str.Length, false) 96 | .ThenBy(x => x.Value, false) 97 | .Select(x => x.Str); 98 | 99 | Assert.That(result, Is.EqualTo(new[] {"abra", "world", "hello"})); 100 | } 101 | 102 | [Test] 103 | public void ThenBy_orders_ascending_when_flag_is_true() 104 | { 105 | var objs = GetSampleObjects(); 106 | 107 | var result = objs.OrderBy(x => x.Str.Length, false) 108 | .ThenBy(x => x.Value, true) 109 | .Select(x => x.Str); 110 | 111 | Assert.That(result, Is.EqualTo(new[] {"abra", "hello", "world"})); 112 | } 113 | 114 | [Test] 115 | public void ThenBy_orders_by_property_name() 116 | { 117 | var objs = GetSampleObjects(); 118 | 119 | var result = objs.OrderBy("Str.Length", true) 120 | .ThenBy("Str", false) 121 | .Select(x => x.Str); 122 | 123 | Assert.That(result, Is.EqualTo(new[] {"hello", "world", "abra"})); 124 | } 125 | 126 | [Test] 127 | public void ThenBy_orders_by_property_name_descending_when_flag_is_true() 128 | { 129 | var objs = GetSampleObjects(); 130 | 131 | var result = objs.OrderBy("Str.Length", true) 132 | .ThenBy("Str", true) 133 | .Select(x => x.Str); 134 | 135 | Assert.That(result, Is.EqualTo(new[] {"world", "hello", "abra"})); 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /Utils/Random/RandomHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.CompilerServices; 5 | 6 | namespace Impworks.Utils.Random; 7 | 8 | /// 9 | /// Random value provider. 10 | /// 11 | public static class RandomHelper 12 | { 13 | /// 14 | /// Underlying random provider. 15 | /// 16 | private static readonly System.Random _random = new System.Random(DateTime.Now.Millisecond); 17 | 18 | /// 19 | /// Creates a random number between 0.0 and 1.0. 20 | /// 21 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 22 | public static float Float() 23 | { 24 | return (float) _random.NextDouble(); 25 | } 26 | 27 | /// 28 | /// Creates a random number between two values. 29 | /// 30 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 31 | public static float Float(float from, float to) 32 | { 33 | var scale = to - from; 34 | return from + (float)_random.NextDouble() * scale; 35 | } 36 | 37 | /// 38 | /// Creates a random number between 0.0 and 1.0. 39 | /// 40 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 41 | public static double Double() 42 | { 43 | return _random.NextDouble(); 44 | } 45 | 46 | /// 47 | /// Creates a random number between two values. 48 | /// 49 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 50 | public static double Double(double from, double to) 51 | { 52 | var scale = to - from; 53 | return from + _random.NextDouble() * scale; 54 | } 55 | 56 | /// 57 | /// Creates a random integer in the given range. 58 | /// 59 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 60 | public static int Int(int from, int to) 61 | { 62 | return _random.Next(from, to); 63 | } 64 | 65 | /// 66 | /// Creates a random long integer in the given range. 67 | /// 68 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 69 | public static long Long(long from, long to) 70 | { 71 | var scale = to - from; 72 | return from + (long) (_random.NextDouble() * scale); 73 | } 74 | 75 | /// 76 | /// Flips a virtual coin. 77 | /// 78 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 79 | public static bool Bool() 80 | { 81 | return _random.NextDouble() > 0.5; 82 | } 83 | 84 | /// 85 | /// Random sign: plus or minus. 86 | /// 87 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 88 | public static int Sign() 89 | { 90 | return _random.NextDouble() > 0.5 ? 1 : -1; 91 | } 92 | 93 | /// 94 | /// Pick a random item from the array. 95 | /// 96 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 97 | public static T PickAny(params T[] items) 98 | { 99 | return Pick(items); 100 | } 101 | 102 | /// 103 | /// Pick a random item from the list. 104 | /// 105 | public static T Pick(IReadOnlyList items) 106 | { 107 | if (items.Count == 0) 108 | return default; 109 | 110 | // works better on bigger numbers 111 | var id = _random.Next(items.Count * 100); 112 | return items[id / 100]; 113 | } 114 | 115 | /// 116 | /// Picks a random item from the list according to their relative weights. 117 | /// 118 | /// Source item collection. 119 | /// 120 | /// Projection that returns a relative weight of the element. 121 | /// Elements with bigger weight are more likely to be selected. 122 | /// 123 | public static T PickWeighted(IReadOnlyList items, Func weightFunc) 124 | { 125 | var threshold = Float(); 126 | var list = items.Select(x => new {Value = x, Weight = weightFunc(x)}).ToList(); 127 | var delta = 1.0 / list.Sum(x => x.Weight); 128 | var prob = 0.0; 129 | 130 | foreach (var elem in list) 131 | { 132 | // normalized weight 133 | prob += elem.Weight * delta; 134 | 135 | if (prob >= threshold) 136 | return elem.Value; 137 | } 138 | 139 | return default; 140 | } 141 | 142 | /// 143 | /// Returns the value in 0..1 range with normal distribution around 0.5. 144 | /// 145 | public static double DoubleNormal() 146 | { 147 | var u1 = 1.0 - Double(); 148 | var u2 = 1.0 - Double(); 149 | var normal = Math.Sqrt(-2.0 * Math.Log(u1)) * Math.Sin(2.0 * Math.PI * u2); 150 | var scaled = 0.5 + normal / 8.0; 151 | return Math.Min(1, Math.Max(scaled, 0)); 152 | } 153 | } -------------------------------------------------------------------------------- /Utils/Strings/StringHelper.Parse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Xml.Linq; 5 | 6 | namespace Impworks.Utils.Strings; 7 | 8 | public static partial class StringHelper 9 | { 10 | /// 11 | /// Gets the parse function for a specific type. 12 | /// 13 | public static Func GetParseFunction() 14 | { 15 | var type = typeof(T); 16 | if (ParseFuncs.TryGetValue(type, out var func)) 17 | return (Func)func; 18 | 19 | if (type.IsEnum) 20 | return x => (T)Enum.Parse(type, x, true); 21 | 22 | if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) 23 | { 24 | var innerType = type.GetGenericArguments()[0]; 25 | if (innerType.IsEnum) 26 | return x => (T)Enum.Parse(innerType, x, true); 27 | } 28 | 29 | throw new Exception($"No parser function found for type '{type.Name}'."); 30 | } 31 | 32 | /// 33 | /// Predefined parser functions. 34 | /// 35 | private static Dictionary ParseFuncs = new Dictionary 36 | { 37 | [typeof(bool)] = (Func)(bool.Parse), 38 | [typeof(int)] = (Func)(x => int.Parse(x, CultureInfo.InvariantCulture)), 39 | [typeof(uint)] = (Func)(x => uint.Parse(x, CultureInfo.InvariantCulture)), 40 | [typeof(long)] = (Func)(x => long.Parse(x, CultureInfo.InvariantCulture)), 41 | [typeof(ulong)] = (Func)(x => ulong.Parse(x, CultureInfo.InvariantCulture)), 42 | [typeof(float)] = (Func)(x => float.Parse(x.Replace(',', '.'), CultureInfo.InvariantCulture)), 43 | [typeof(double)] = (Func)(x => double.Parse(x.Replace(',', '.'), CultureInfo.InvariantCulture)), 44 | [typeof(byte)] = (Func)(x => byte.Parse(x, CultureInfo.InvariantCulture)), 45 | [typeof(sbyte)] = (Func)(x => sbyte.Parse(x, CultureInfo.InvariantCulture)), 46 | [typeof(char)] = (Func)(char.Parse), 47 | [typeof(decimal)] = (Func)(x => decimal.Parse(x, CultureInfo.InvariantCulture)), 48 | [typeof(DateTime)] = (Func)(x => DateTime.Parse(x, CultureInfo.InvariantCulture)), 49 | [typeof(DateTimeOffset)] = (Func)(x => DateTime.Parse(x, CultureInfo.InvariantCulture)), 50 | [typeof(TimeSpan)] = (Func)(x => TimeSpan.Parse(x, CultureInfo.InvariantCulture)), 51 | [typeof(Guid)] = (Func)(Guid.Parse), 52 | 53 | [typeof(bool?)] = (Func)(x => bool.Parse(x)), 54 | [typeof(int?)] = (Func)(x => int.Parse(x, CultureInfo.InvariantCulture)), 55 | [typeof(uint?)] = (Func)(x => uint.Parse(x, CultureInfo.InvariantCulture)), 56 | [typeof(long?)] = (Func)(x => long.Parse(x, CultureInfo.InvariantCulture)), 57 | [typeof(ulong?)] = (Func)(x => ulong.Parse(x, CultureInfo.InvariantCulture)), 58 | [typeof(float?)] = (Func)(x => float.Parse(x.Replace(',', '.'), CultureInfo.InvariantCulture)), 59 | [typeof(double?)] = (Func)(x => double.Parse(x.Replace(',', '.'), CultureInfo.InvariantCulture)), 60 | [typeof(byte?)] = (Func)(x => byte.Parse(x, CultureInfo.InvariantCulture)), 61 | [typeof(sbyte?)] = (Func)(x => sbyte.Parse(x, CultureInfo.InvariantCulture)), 62 | [typeof(char?)] = (Func)(x => char.Parse(x)), 63 | [typeof(decimal?)] = (Func)(x => decimal.Parse(x, CultureInfo.InvariantCulture)), 64 | [typeof(DateTime?)] = (Func)(x => DateTime.Parse(x, CultureInfo.InvariantCulture)), 65 | [typeof(DateTimeOffset?)] = (Func)(x => DateTime.Parse(x, CultureInfo.InvariantCulture)), 66 | [typeof(TimeSpan?)] = (Func)(x => TimeSpan.Parse(x, CultureInfo.InvariantCulture)), 67 | [typeof(Guid?)] = (Func)(x => Guid.Parse(x)), 68 | 69 | [typeof(string)] = (Func)(x => x), 70 | [typeof(XElement)] = (Func)(XElement.Parse), 71 | [typeof(XDocument)] = (Func)(XDocument.Parse), 72 | [typeof(Uri)] = (Func)(x => new Uri(x, UriKind.RelativeOrAbsolute)), 73 | 74 | #if NET6_0_OR_GREATER 75 | [typeof(DateOnly)] = (Func)(x => DateOnly.Parse(x, CultureInfo.InvariantCulture)), 76 | [typeof(DateOnly?)] = (Func)(x => DateOnly.Parse(x, CultureInfo.InvariantCulture)), 77 | [typeof(TimeOnly)] = (Func)(x => TimeOnly.Parse(x, CultureInfo.InvariantCulture)), 78 | [typeof(TimeOnly?)] = (Func)(x => TimeOnly.Parse(x, CultureInfo.InvariantCulture)), 79 | 80 | [typeof(Half)] = (Func)(x => Half.Parse(x, CultureInfo.InvariantCulture)), 81 | [typeof(Half?)] = (Func)(x => Half.Parse(x, CultureInfo.InvariantCulture)), 82 | #endif 83 | }; 84 | } -------------------------------------------------------------------------------- /Utils.Tests/Linq/EnumerableExtensions_FirstNOrDefault.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Impworks.Utils.Linq; 3 | using NUnit.Framework; 4 | 5 | namespace Utils.Tests.Linq 6 | { 7 | /// 8 | /// Tests for EnumerableExtensions.FirstN. 9 | /// 10 | [TestFixture] 11 | public class EnumerableExtensions_FirstNOrDefault 12 | { 13 | [Test] 14 | [TestCase(new [] { 1, 2 })] 15 | [TestCase(new [] { 1, 2, 3 })] 16 | public void First2OrDefault_returns_two_values(int[] array) 17 | { 18 | var (a, b) = array.First2OrDefault(); 19 | Assert.That(a, Is.EqualTo(1)); 20 | Assert.That(b, Is.EqualTo(2)); 21 | } 22 | 23 | [Test] 24 | public void First2OrDefault_returns_default_for_smaller_arrays() 25 | { 26 | var (a, b) = new [] { 1 }.First2OrDefault(); 27 | Assert.That(a, Is.EqualTo(1)); 28 | Assert.That(b, Is.EqualTo(0)); 29 | } 30 | 31 | [Test] 32 | [TestCase(new [] { 1, 2, 3 })] 33 | [TestCase(new [] { 1, 2, 3, 4 })] 34 | public void First3OrDefault_returns_three_values(int[] array) 35 | { 36 | var (a, b, c) = array.First3OrDefault(); 37 | Assert.That(a, Is.EqualTo(1)); 38 | Assert.That(b, Is.EqualTo(2)); 39 | Assert.That(c, Is.EqualTo(3)); 40 | } 41 | 42 | [Test] 43 | public void First3OrDefault_returns_default_for_smaller_arrays() 44 | { 45 | var (a, b, c) = new [] { 1 }.First3OrDefault(); 46 | Assert.That(a, Is.EqualTo(1)); 47 | Assert.That(b, Is.EqualTo(0)); 48 | Assert.That(c, Is.EqualTo(0)); 49 | } 50 | 51 | [Test] 52 | [TestCase(new [] { 1, 2, 3, 4 })] 53 | [TestCase(new [] { 1, 2, 3, 4, 5 })] 54 | public void First4OrDefault_returns_four_values(int[] array) 55 | { 56 | var (a, b, c, d) = array.First4OrDefault(); 57 | Assert.That(a, Is.EqualTo(1)); 58 | Assert.That(b, Is.EqualTo(2)); 59 | Assert.That(c, Is.EqualTo(3)); 60 | Assert.That(d, Is.EqualTo(4)); 61 | } 62 | 63 | [Test] 64 | public void First4OrDefault_returns_default_for_smaller_arrays() 65 | { 66 | var (a, b, c, d) = new [] { 1 }.First4OrDefault(); 67 | Assert.That(a, Is.EqualTo(1)); 68 | Assert.That(b, Is.EqualTo(0)); 69 | Assert.That(c, Is.EqualTo(0)); 70 | Assert.That(d, Is.EqualTo(0)); 71 | } 72 | 73 | [Test] 74 | [TestCase(new [] { 1, 2, 3, 4, 5 })] 75 | [TestCase(new [] { 1, 2, 3, 4, 5, 6 })] 76 | public void First5OrDefault_returns_five_values(int[] array) 77 | { 78 | var (a, b, c, d, e) = array.First5OrDefault(); 79 | Assert.That(a, Is.EqualTo(1)); 80 | Assert.That(b, Is.EqualTo(2)); 81 | Assert.That(c, Is.EqualTo(3)); 82 | Assert.That(d, Is.EqualTo(4)); 83 | Assert.That(e, Is.EqualTo(5)); 84 | } 85 | 86 | [Test] 87 | public void First5OrDefault_returns_default_for_smaller_arrays() 88 | { 89 | var (a, b, c, d, e) = new [] { 1 }.First5OrDefault(); 90 | Assert.That(a, Is.EqualTo(1)); 91 | Assert.That(b, Is.EqualTo(0)); 92 | Assert.That(c, Is.EqualTo(0)); 93 | Assert.That(d, Is.EqualTo(0)); 94 | Assert.That(e, Is.EqualTo(0)); 95 | } 96 | 97 | [Test] 98 | [TestCase(new [] { 1, 2, 3, 4, 5, 6 })] 99 | [TestCase(new [] { 1, 2, 3, 4, 5, 6, 7 })] 100 | public void First6OrDefault_returns_six_values(int[] array) 101 | { 102 | var (a, b, c, d, e, f) = array.First6OrDefault(); 103 | Assert.That(a, Is.EqualTo(1)); 104 | Assert.That(b, Is.EqualTo(2)); 105 | Assert.That(c, Is.EqualTo(3)); 106 | Assert.That(d, Is.EqualTo(4)); 107 | Assert.That(e, Is.EqualTo(5)); 108 | Assert.That(f, Is.EqualTo(6)); 109 | } 110 | 111 | [Test] 112 | public void First6OrDefault_returns_default_for_smaller_arrays() 113 | { 114 | var (a, b, c, d, e, f) = new [] { 1 }.First6OrDefault(); 115 | Assert.That(a, Is.EqualTo(1)); 116 | Assert.That(b, Is.EqualTo(0)); 117 | Assert.That(c, Is.EqualTo(0)); 118 | Assert.That(d, Is.EqualTo(0)); 119 | Assert.That(e, Is.EqualTo(0)); 120 | Assert.That(f, Is.EqualTo(0)); 121 | } 122 | 123 | [Test] 124 | [TestCase(new [] { 1, 2, 3, 4, 5, 6, 7 })] 125 | [TestCase(new [] { 1, 2, 3, 4, 5, 6, 7, 8 })] 126 | public void First7OrDefault_returns_seven_values(int[] array) 127 | { 128 | var (a, b, c, d, e, f, g) = array.First7OrDefault(); 129 | Assert.That(a, Is.EqualTo(1)); 130 | Assert.That(b, Is.EqualTo(2)); 131 | Assert.That(c, Is.EqualTo(3)); 132 | Assert.That(d, Is.EqualTo(4)); 133 | Assert.That(e, Is.EqualTo(5)); 134 | Assert.That(f, Is.EqualTo(6)); 135 | Assert.That(g, Is.EqualTo(7)); 136 | } 137 | 138 | [Test] 139 | public void First7OrDefault_returns_default_for_smaller_arrays() 140 | { 141 | var (a, b, c, d, e, f, g) = new [] { 1 }.First7OrDefault(); 142 | Assert.That(a, Is.EqualTo(1)); 143 | Assert.That(b, Is.EqualTo(0)); 144 | Assert.That(c, Is.EqualTo(0)); 145 | Assert.That(d, Is.EqualTo(0)); 146 | Assert.That(e, Is.EqualTo(0)); 147 | Assert.That(f, Is.EqualTo(0)); 148 | Assert.That(g, Is.EqualTo(0)); 149 | } 150 | } 151 | } -------------------------------------------------------------------------------- /Utils/Xml/XmlHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.IO; 4 | using System.Text; 5 | using System.Text.RegularExpressions; 6 | using System.Xml; 7 | using System.Xml.Serialization; 8 | 9 | namespace Impworks.Utils.Xml; 10 | 11 | /// 12 | /// Helper methods for (de)serializing objects from XML. 13 | /// 14 | public static class XmlHelper 15 | { 16 | static XmlHelper() 17 | { 18 | SerializersCache = new ConcurrentDictionary, XmlSerializer>(); 19 | EmptyNamespaces = new XmlSerializerNamespaces([XmlQualifiedName.Empty]); 20 | NilCleanRegex = new Regex( 21 | """\s*(xmlns:[a-z0-9]+="http://www\.w3\.org/2001/XMLSchema-instance"|[a-z0-9]+:nil="true")""", 22 | RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.ExplicitCapture 23 | ); 24 | } 25 | 26 | /// 27 | /// Global cache for Serializer instances. 28 | /// 29 | private static readonly ConcurrentDictionary, XmlSerializer> SerializersCache; 30 | 31 | /// 32 | /// Empty namespaces setting. 33 | /// 34 | private static readonly XmlSerializerNamespaces EmptyNamespaces; 35 | 36 | /// 37 | /// Regular expression for removing "nil" notification for empty fields. 38 | /// 39 | private static readonly Regex NilCleanRegex; 40 | 41 | #region Serialize 42 | 43 | /// 44 | /// Converts the object to an XML representation. 45 | /// 46 | /// 47 | /// Object to serialize. 48 | /// Serializer. 49 | /// Flag indicating that the XML must be cleaned up. This removes the declaration, namespaces and "nil" values. 50 | /// Flag indicating that the XML must be indented. 51 | /// Encoding to use. Defaults to UTF-8. 52 | /// Serialized object. 53 | public static string Serialize(object obj, XmlSerializer serializer = null, bool clean = true, bool indent = true, Encoding enc = null) 54 | { 55 | if (obj == null) 56 | throw new ArgumentNullException(nameof(obj)); 57 | 58 | if (serializer == null) 59 | serializer = GetSerializer(obj.GetType()); 60 | 61 | var options = new XmlWriterSettings 62 | { 63 | OmitXmlDeclaration = clean, 64 | Indent = indent, 65 | Encoding = enc ?? Encoding.UTF8 66 | }; 67 | 68 | using var sw = new StringWriter(); 69 | using var xw = XmlWriter.Create(sw, options); 70 | 71 | serializer.Serialize(xw, obj, clean ? EmptyNamespaces : null); 72 | var result = sw.ToString(); 73 | 74 | if (clean) 75 | result = NilCleanRegex.Replace(result, ""); 76 | 77 | return result; 78 | } 79 | 80 | /// 81 | /// Converts the object to an XML representation. 82 | /// 83 | /// 84 | /// Object to serialize. 85 | /// Name of the root element. 86 | /// Flag indicating that the XML must be cleaned up. This removes the declaration, namespaces and "nil" values. 87 | /// Flag indicating that the XML must be indented. 88 | /// Encoding to use. Defaults to UTF-8. 89 | /// Serialized object. 90 | public static string Serialize(object obj, string rootName, bool clean = true, bool indent = true, Encoding enc = null) 91 | { 92 | if (obj == null) 93 | throw new ArgumentNullException(nameof(obj)); 94 | 95 | var ser = GetSerializer(obj.GetType(), rootName); 96 | return Serialize(obj, ser, clean, indent, enc); 97 | } 98 | 99 | #endregion 100 | 101 | #region Deserialization 102 | 103 | /// 104 | /// Deserializes the object from XML. 105 | /// 106 | /// 107 | /// XML representation of the object. 108 | /// Serializer. 109 | /// Deserialized object. 110 | public static T Deserialize(string xml, XmlSerializer serializer = null) 111 | { 112 | if(xml == null) 113 | throw new ArgumentNullException(nameof(xml)); 114 | 115 | if(serializer == null) 116 | serializer = GetSerializer(typeof(T)); 117 | 118 | using var sr = new StringReader(xml); 119 | return (T) serializer.Deserialize(sr); 120 | } 121 | 122 | /// 123 | /// Deserializes the object from XML. 124 | /// 125 | /// 126 | /// XML representation of the object. 127 | /// Name of the root element. 128 | /// Deserialized object. 129 | public static T Deserialize(string xml, string rootName) 130 | { 131 | var ser = GetSerializer(typeof(T), rootName); 132 | return Deserialize(xml, ser); 133 | } 134 | 135 | #endregion 136 | 137 | #region Serializer caching 138 | 139 | /// 140 | /// Returns the appropriate serializer. 141 | /// 142 | private static XmlSerializer GetSerializer(Type type, string name = null) 143 | { 144 | var key = Tuple.Create(type, name); 145 | return SerializersCache.GetOrAdd(key, k => Create()); 146 | 147 | XmlSerializer Create() 148 | { 149 | return string.IsNullOrEmpty(name) 150 | ? new XmlSerializer(type) 151 | : new XmlSerializer(type, new XmlRootAttribute(name)); 152 | } 153 | } 154 | 155 | #endregion 156 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | # NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | [Dd]ebugPS/ 42 | [Rr]eleasePS/ 43 | dlldata.c 44 | 45 | # .NET Core 46 | project.lock.json 47 | project.fragment.lock.json 48 | artifacts/ 49 | **/Properties/launchSettings.json 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # Visual Studio code coverage results 117 | *.coverage 118 | *.coveragexml 119 | 120 | # NCrunch 121 | _NCrunch_* 122 | .*crunch*.local.xml 123 | nCrunchTemp_* 124 | 125 | # MightyMoose 126 | *.mm.* 127 | AutoTest.Net/ 128 | 129 | # Web workbench (sass) 130 | .sass-cache/ 131 | 132 | # Installshield output folder 133 | [Ee]xpress/ 134 | 135 | # DocProject is a documentation generator add-in 136 | DocProject/buildhelp/ 137 | DocProject/Help/*.HxT 138 | DocProject/Help/*.HxC 139 | DocProject/Help/*.hhc 140 | DocProject/Help/*.hhk 141 | DocProject/Help/*.hhp 142 | DocProject/Help/Html2 143 | DocProject/Help/html 144 | 145 | # Click-Once directory 146 | publish/ 147 | 148 | # Publish Web Output 149 | *.[Pp]ublish.xml 150 | *.azurePubxml 151 | # TODO: Comment the next line if you want to checkin your web deploy settings 152 | # but database connection strings (with potential passwords) will be unencrypted 153 | *.pubxml 154 | *.publishproj 155 | 156 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 157 | # checkin your Azure Web App publish settings, but sensitive information contained 158 | # in these scripts will be unencrypted 159 | PublishScripts/ 160 | 161 | # NuGet Packages 162 | *.nupkg 163 | # The packages folder can be ignored because of Package Restore 164 | **/packages/* 165 | # except build/, which is used as an MSBuild target. 166 | !**/packages/build/ 167 | # Uncomment if necessary however generally it will be regenerated when needed 168 | #!**/packages/repositories.config 169 | # NuGet v3's project.json files produces more ignorable files 170 | *.nuget.props 171 | *.nuget.targets 172 | 173 | # Microsoft Azure Build Output 174 | csx/ 175 | *.build.csdef 176 | 177 | # Microsoft Azure Emulator 178 | ecf/ 179 | rcf/ 180 | 181 | # Windows Store app package directories and files 182 | AppPackages/ 183 | BundleArtifacts/ 184 | Package.StoreAssociation.xml 185 | _pkginfo.txt 186 | 187 | # Visual Studio cache files 188 | # files ending in .cache can be ignored 189 | *.[Cc]ache 190 | # but keep track of directories ending in .cache 191 | !*.[Cc]ache/ 192 | 193 | # Others 194 | ClientBin/ 195 | ~$* 196 | *~ 197 | *.dbmdl 198 | *.dbproj.schemaview 199 | *.jfm 200 | *.pfx 201 | *.publishsettings 202 | orleans.codegen.cs 203 | 204 | # Since there are multiple workflows, uncomment next line to ignore bower_components 205 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 206 | #bower_components/ 207 | 208 | # RIA/Silverlight projects 209 | Generated_Code/ 210 | 211 | # Backup & report files from converting an old project file 212 | # to a newer Visual Studio version. Backup files are not needed, 213 | # because we have git ;-) 214 | _UpgradeReport_Files/ 215 | Backup*/ 216 | UpgradeLog*.XML 217 | UpgradeLog*.htm 218 | 219 | # SQL Server files 220 | *.mdf 221 | *.ldf 222 | *.ndf 223 | 224 | # Business Intelligence projects 225 | *.rdl.data 226 | *.bim.layout 227 | *.bim_*.settings 228 | 229 | # Microsoft Fakes 230 | FakesAssemblies/ 231 | 232 | # GhostDoc plugin setting file 233 | *.GhostDoc.xml 234 | 235 | # Node.js Tools for Visual Studio 236 | .ntvs_analysis.dat 237 | node_modules/ 238 | 239 | # Typescript v1 declaration files 240 | typings/ 241 | 242 | # Visual Studio 6 build log 243 | *.plg 244 | 245 | # Visual Studio 6 workspace options file 246 | *.opt 247 | 248 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 249 | *.vbw 250 | 251 | # Visual Studio LightSwitch build output 252 | **/*.HTMLClient/GeneratedArtifacts 253 | **/*.DesktopClient/GeneratedArtifacts 254 | **/*.DesktopClient/ModelManifest.xml 255 | **/*.Server/GeneratedArtifacts 256 | **/*.Server/ModelManifest.xml 257 | _Pvt_Extensions 258 | 259 | # Paket dependency manager 260 | .paket/paket.exe 261 | paket-files/ 262 | 263 | # FAKE - F# Make 264 | .fake/ 265 | 266 | # JetBrains Rider 267 | .idea/ 268 | *.sln.iml 269 | 270 | # CodeRush 271 | .cr/ 272 | 273 | # Python Tools for Visual Studio (PTVS) 274 | __pycache__/ 275 | *.pyc 276 | 277 | # Cake - Uncomment if you are using it 278 | # tools/** 279 | # !tools/packages.config 280 | 281 | # Telerik's JustMock configuration file 282 | *.jmconfig 283 | 284 | # BizTalk build output 285 | *.btp.cs 286 | *.btm.cs 287 | *.odx.cs 288 | *.xsd.cs 289 | -------------------------------------------------------------------------------- /Utils.Tests/Strings/StringExtensions_Parse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Impworks.Utils.Strings; 4 | using NUnit.Framework; 5 | 6 | namespace Utils.Tests.Strings 7 | { 8 | /// 9 | /// Tests for StringExtensions.Parse. 10 | /// 11 | [TestFixture] 12 | public class StringExtensions_Parse 13 | { 14 | private static IEnumerable ParseTestCases() 15 | { 16 | yield return ("123", 123); 17 | yield return ("123", (uint)123); 18 | yield return ("123", (long)123); 19 | yield return ("123", (ulong)123); 20 | yield return ("123", (byte)123); 21 | yield return ("123", (sbyte)123); 22 | yield return ("123", (decimal)123); 23 | yield return ("1.2", 1.2); 24 | yield return ("1.2", (float)1.2); 25 | yield return ("1.23", (decimal)1.23); 26 | 27 | yield return ("true", true); 28 | yield return ("false", false); 29 | yield return ("TRUE", true); 30 | yield return ("FALSE", false); 31 | yield return ("x", 'x'); 32 | 33 | yield return ("2018-05-18", new DateTime(2018, 05, 18)); 34 | yield return ("2018-05-18 12:34:56", new DateTime(2018, 05, 18, 12, 34, 56)); 35 | yield return ("2018-05-18", new DateTimeOffset(new DateTime(2018, 05, 18))); 36 | 37 | yield return ("90b3a536-da16-4238-b781-cee864a9ec00", new Guid("90b3a536-da16-4238-b781-cee864a9ec00")); 38 | 39 | yield return ("1", SampleEnum.Foo); 40 | yield return ("Bar", SampleEnum.Bar); 41 | yield return ("bar", SampleEnum.Bar); 42 | 43 | yield return ("123", (int?)123); 44 | yield return ("123", (uint?)123); 45 | yield return ("123", (long?)123); 46 | yield return ("123", (ulong?)123); 47 | yield return ("123", (byte?)123); 48 | yield return ("123", (sbyte?)123); 49 | yield return ("123", (decimal?)123); 50 | yield return ("1.2", (double?)1.2); 51 | yield return ("1.2", (float?)1.2); 52 | yield return ("1.23", (decimal?)1.23); 53 | 54 | yield return ("true", (bool?)true); 55 | yield return ("false", (bool?) false); 56 | yield return ("TRUE", (bool?) true); 57 | yield return ("FALSE", (bool?) false); 58 | yield return ("x", (char?)'x'); 59 | 60 | yield return ("2018-05-18", (DateTime?) new DateTime(2018, 05, 18)); 61 | yield return ("2018-05-18 12:34:56", (DateTime?) new DateTime(2018, 05, 18, 12, 34, 56)); 62 | yield return ("2018-05-18", (DateTimeOffset?) new DateTimeOffset(new DateTime(2018, 05, 18))); 63 | 64 | yield return ("90b3a536-da16-4238-b781-cee864a9ec00", (Guid?) new Guid("90b3a536-da16-4238-b781-cee864a9ec00")); 65 | 66 | yield return ("1", (SampleEnum?) SampleEnum.Foo); 67 | yield return ("Bar", (SampleEnum?) SampleEnum.Bar); 68 | yield return ("bar", (SampleEnum?) SampleEnum.Bar); 69 | 70 | yield return ("foo", "foo"); 71 | yield return ("http://example.com", new Uri("http://example.com")); 72 | yield return ("test/foo", new Uri("test/foo", UriKind.RelativeOrAbsolute)); 73 | 74 | #if NET6_0_OR_GREATER 75 | yield return ("123", (Half)123); 76 | yield return ("123", (Half?)123); 77 | yield return ("2018-05-18", new DateOnly(2018, 05, 18)); 78 | yield return ("2018-05-18", (DateOnly?) new DateOnly(2018, 05, 18)); 79 | yield return ("16:47:23", new TimeOnly(16, 47, 23)); 80 | yield return ("16:47:23", (TimeOnly?) new TimeOnly(16, 47, 23)); 81 | #endif 82 | } 83 | 84 | [Test] 85 | [TestCaseSource(typeof(StringExtensions_Parse), nameof(ParseTestCases))] 86 | public void Parse_parses_values(dynamic tuple) 87 | { 88 | // required for automatic generic type inference via dynamics 89 | Check(tuple); 90 | } 91 | 92 | [Test] 93 | public void Parse_accepts_parse_function() 94 | { 95 | Assert.That("1-2-3".Parse(x => int.Parse(x.Replace("-", ""))), Is.EqualTo(123)); 96 | } 97 | 98 | [Test] 99 | public void Parse_throws_error_on_incorrect_value() 100 | { 101 | Assert.Throws(() => "1-2-3".Parse()); 102 | } 103 | 104 | [Test] 105 | public void Parse_throws_error_on_null() 106 | { 107 | Assert.Throws(() => (null as string).Parse()); 108 | } 109 | 110 | [Test] 111 | public void TryParse_accepts_parse_function() 112 | { 113 | Assert.That("1-2-3".TryParse(x => int.Parse(x.Replace("-", ""))), Is.EqualTo(123)); 114 | } 115 | 116 | [Test] 117 | public void TryParse_returns_default_on_incorrect_value() 118 | { 119 | Assert.That("1-2-3".TryParse(), Is.EqualTo(0)); 120 | Assert.That("1-2-3".TryParse(), Is.Null); 121 | } 122 | 123 | [Test] 124 | public void TryParse_accepts_null() 125 | { 126 | Assert.That((null as string).TryParse(), Is.EqualTo(0)); 127 | Assert.That((null as string).TryParse(), Is.Null); 128 | } 129 | 130 | [Test] 131 | public void TryParseList_returns_list() 132 | { 133 | Assert.That("1,2,3".TryParseList(), Is.EqualTo(new [] { 1, 2, 3 })); 134 | } 135 | 136 | [Test] 137 | public void TryParseList_skips_failed_entries() 138 | { 139 | Assert.That("1,test,3".TryParseList(), Is.EqualTo(new[] { 1, 3 })); 140 | } 141 | 142 | [Test] 143 | public void TryParseList_returns_empty_list_if_no_item_succeeded() 144 | { 145 | Assert.That("a,b,c".TryParseList(), Is.EqualTo(new int[0])); 146 | } 147 | 148 | [Test] 149 | public void TryParseList_uses_separator() 150 | { 151 | Assert.That("1-2-3".TryParseList(separator: "-"), Is.EqualTo(new[] { 1, 2, 3 })); 152 | } 153 | 154 | [Test] 155 | public void TryParseList_uses_parseFunc() 156 | { 157 | Assert.That("@1,@2,@3".TryParseList(parseFunc: str => int.Parse(str.TrimStart('@'))), Is.EqualTo(new[] { 1, 2, 3 })); 158 | } 159 | 160 | [Test] 161 | public void TryParseList_accepts_null() 162 | { 163 | Assert.That((null as string).TryParseList(), Is.EqualTo(new int[0])); 164 | } 165 | 166 | void Check((string str, T obj) value) 167 | { 168 | Assert.That(value.str.Parse(), Is.EqualTo(value.obj)); 169 | } 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /Utils/Linq/EnumerableExtensions.Order.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | using System.Runtime.CompilerServices; 7 | 8 | namespace Impworks.Utils.Linq; 9 | 10 | public static partial class EnumerableExtensions 11 | { 12 | /// 13 | /// Orders the sequence by a field with direction based on a flag. 14 | /// 15 | /// The sequence of values. 16 | /// Descriptor of the property to use for sorting. 17 | /// Order direction flag. 18 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 19 | public static IOrderedEnumerable OrderBy(this IEnumerable source, Func orderExpr, bool isDescending) 20 | { 21 | return isDescending 22 | ? source.OrderByDescending(orderExpr) 23 | : source.OrderBy(orderExpr); 24 | } 25 | 26 | /// 27 | /// Orders the sequence by a field with direction based on a flag. 28 | /// 29 | /// The sequence of values. 30 | /// Descriptor of the property to use for sorting. 31 | /// Order direction flag. 32 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 33 | public static IOrderedEnumerable ThenBy(this IOrderedEnumerable source, Func orderExpr, bool isDescending) 34 | { 35 | return isDescending 36 | ? source.ThenByDescending(orderExpr) 37 | : source.ThenBy(orderExpr); 38 | } 39 | 40 | /// 41 | /// Orders the query by a field with direction based on a flag. 42 | /// 43 | /// The sequence of values. 44 | /// Descriptor of the property to use for sorting. 45 | /// Order direction flag. 46 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 47 | public static IOrderedQueryable OrderBy(this IQueryable source, Expression> orderExpr, bool isDescending) 48 | { 49 | return isDescending 50 | ? source.OrderByDescending(orderExpr) 51 | : source.OrderBy(orderExpr); 52 | } 53 | 54 | /// 55 | /// Orders the query by a field with direction based on a flag. 56 | /// 57 | /// The sequence of values. 58 | /// Descriptor of the property to use for sorting. 59 | /// Order direction flag. 60 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 61 | public static IOrderedQueryable ThenBy(this IOrderedQueryable source, Expression> orderExpr, bool isDescending) 62 | { 63 | return isDescending 64 | ? source.ThenByDescending(orderExpr) 65 | : source.ThenBy(orderExpr); 66 | } 67 | 68 | /// 69 | /// Orders the query by a field name with direction based on a flag. 70 | /// 71 | /// The sequence of values. 72 | /// Name of the property to use for sorting. 73 | /// Order direction flag. 74 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 75 | public static IOrderedQueryable OrderBy(this IQueryable source, string propName, bool isDescending) 76 | { 77 | var method = isDescending ? OrderMethods.OrderByDescending : OrderMethods.OrderBy; 78 | return OrderByInternal(source, propName, method); 79 | } 80 | 81 | /// 82 | /// Orders the query by a field name with direction based on a flag. 83 | /// 84 | /// The sequence of values. 85 | /// Name of the property to use for sorting. 86 | /// Order direction flag. 87 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 88 | public static IOrderedQueryable ThenBy(this IOrderedQueryable source, string propName, bool isDescending) 89 | { 90 | var method = isDescending ? OrderMethods.ThenByDescending : OrderMethods.ThenBy; 91 | return OrderByInternal(source, propName, method); 92 | } 93 | 94 | #region Private helpers 95 | 96 | /// 97 | /// The cached references to ordering methods. 98 | /// 99 | private static class OrderMethods 100 | { 101 | static OrderMethods() 102 | { 103 | var methods = typeof(Queryable).GetMethods() 104 | .Where(x => x.GetParameters().Length == 2 105 | && (x.Name.StartsWith("OrderBy") || x.Name.StartsWith("ThenBy"))) 106 | .ToDictionary(x => x.Name, x => x); 107 | 108 | OrderBy = methods[nameof(OrderBy)]; 109 | OrderByDescending = methods[nameof(OrderByDescending)]; 110 | ThenBy = methods[nameof(ThenBy)]; 111 | ThenByDescending = methods[nameof(ThenByDescending)]; 112 | } 113 | 114 | public static readonly MethodInfo OrderBy; 115 | public static readonly MethodInfo OrderByDescending; 116 | public static readonly MethodInfo ThenBy; 117 | public static readonly MethodInfo ThenByDescending; 118 | } 119 | 120 | /// 121 | /// Applies the arbitrary ordering method with arbitrary property. 122 | /// 123 | private static IOrderedQueryable OrderByInternal(IQueryable source, string propName, MethodInfo method) 124 | { 125 | var arg = Expression.Parameter(typeof(T), "x"); 126 | var expr = (Expression) arg; 127 | var type = typeof(T); 128 | 129 | var parts = propName.Split('.'); 130 | foreach (var part in parts) 131 | { 132 | var propType = type.GetProperty(part)?.PropertyType 133 | ?? type.GetField(part)?.FieldType; 134 | 135 | if(propType == null) 136 | throw new ArgumentException($"Invalid property path ('{propName}'): type '{type.Name}' has no property or field named '{part}'."); 137 | 138 | expr = Expression.PropertyOrField(expr, part); 139 | type = propType; 140 | } 141 | 142 | var lambda = Expression.Lambda(expr, arg); 143 | var query = source.Provider.CreateQuery( 144 | Expression.Call( 145 | null, 146 | method.MakeGenericMethod(typeof(T), type), 147 | [ 148 | source.Expression, 149 | Expression.Quote(lambda) 150 | ] 151 | ) 152 | ); 153 | 154 | return (IOrderedQueryable) query; 155 | } 156 | 157 | #endregion 158 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Impworks.Utils 2 | 3 | ![AppVeyor](https://img.shields.io/appveyor/ci/impworks/Utils.svg) ![AppVeyor Tests](https://img.shields.io/appveyor/tests/impworks/Utils.svg) [![NuGet](https://img.shields.io/nuget/v/Impworks.Utils.svg)](https://www.nuget.org/packages/Impworks.Utils/) ![NuGet Downloads](https://img.shields.io/nuget/dt/Impworks.Utils.svg) 4 | 5 | A collection of useful helper methods. 6 | 7 | ## Installation 8 | 9 | To install, use the following Package Manager command: 10 | 11 | ``` 12 | PM> Install-Package Impworks.Utils 13 | ``` 14 | 15 | ## Usage 16 | 17 | Add the `using` directive with the required namespace to the top of the file where you intend to use a helper method. 18 | 19 | If you work with Resharper, it will suggest adding the namespace automatically. 20 | 21 | ## The methods 22 | 23 | The package provides the following methods, split into logical parts. 24 | 25 | * Strings 26 | 27 | Parsing to various types (primitives supported by default): 28 | ```csharp 29 | "123".Parse() // 123 30 | "hello".TryParse() // null 31 | "12345".TryParse(MyParseFunc) // MyType 32 | "1,2,test,3".TryParseList() // 1, 2, 3 33 | "1;2;test;3".TryParseList(separator: ";") // 1, 2, 3 34 | "1".TryParse(); // MyEnum or null 35 | ``` 36 | Coalesce: 37 | ```csharp 38 | StringHelper.Coalesce(null, "", "test") // test 39 | ``` 40 | Ends/starts with substring: 41 | ```csharp 42 | "hello world".StartsWithPart("hello test", 5) // true 43 | "a test".EndsWithPart("b TEST", 4, ignoreCase: true) // true 44 | ``` 45 | Transliteration from Russian: 46 | ```csharp 47 | StringHelper.Transliterate("Привет мир", "_") // "Privet_mir" 48 | ``` 49 | `string.Join` as extension: 50 | ```csharp 51 | new [] { 1, 2, 3 }.JoinString(", ") // "1, 2, 3" 52 | ``` 53 | 54 | * Enumerable 55 | 56 | `Distinct` by projection: 57 | ```csharp 58 | new [] { 1, 2, 3 }.DistinctBy(x => x % 2) // 1, 2 59 | ``` 60 | Conditional ordering: 61 | ```csharp 62 | new [] { 4, 6, 1 }.OrderBy(x => x, isDescending) // true => 6, 4, 1, false => 1, 4, 6 63 | new [] { obj1, obj2 }.OrderBy("FieldA.FieldB", isDescending) // orders by field or path 64 | ``` 65 | Partitioning: 66 | ```csharp 67 | new [] { 1, 2, 3, 4, 5, 6 }.PartitionBySize(2) // [1, 2], [3, 4], [5, 6] 68 | new [] { 1, 2, 3, 4, 5, 6 }.PartitionByCount(2) => [1, 2, 3], [4, 5, 6] 69 | ``` 70 | Recursive selection and application: 71 | ```csharp 72 | new [] { treeRoot }.SelectRecursively(x => x.Children) // selects all children in a flat list 73 | new [] { treeRoot }.ApplyRecursively(x => x.Children, x => x.Value = 1) // sets Value = 1 on all children 74 | ``` 75 | Destructuring sequences: 76 | ```csharp 77 | var (a, b) = new [] { 1, 2, 3 }.First2(); // a = 1, b = 2 78 | var (c, d, e, f) = new [] { "foo", "bar" }.First4OrDefault(); // e, f = null 79 | ``` 80 | 81 | * Expressions 82 | 83 | Expression combinations (useful for LINQ): 84 | ```csharp 85 | ExprHelper.And(x => x.A == 1, x => x.B == 2) // x => x.A == 1 && x.B == 2 86 | ExprHelper.Or(x => x.A == 1, x => x.B == 2) // x => x.A == 1 || x.B == 2 87 | ``` 88 | Partial application (for `Func` and `Action` up to 8 arguments): 89 | ```csharp 90 | ExprHelper.Apply((int x, int y) => x + y, 10) // Expr for (int x) => x + 10 91 | ``` 92 | 93 | * Enums 94 | 95 | Captions from `Description` attribute: 96 | ```csharp 97 | enum Language 98 | { 99 | [Description("C#")] CSharp, 100 | [Description("C++")] CPlusPlus 101 | } 102 | EnumHelper.GetEnumDescriptions() // { Language.CSharp = "C#", Language.CPlusPlus = "C++" } 103 | ``` 104 | Case-insensitive IsDefined: 105 | ```csharp 106 | EnumHelper.IsDefined("csharp") // true 107 | ``` 108 | 109 | * Exceptions 110 | 111 | Fluent exception ignoring (with async versions too): 112 | ```csharp 113 | Try.Do(() => SomeStuff()); // does not throw 114 | Try.Get(() => GetAnInt()) // returns 0 on exception 115 | Try.Get(() => GetAnInt(), 123) // returns 123 on exception 116 | ``` 117 | 118 | * Comparisons 119 | 120 | Min and Max for all IComparable's: 121 | ```csharp 122 | CompareHelper.Min("b", "a", "c") // "a" 123 | CompareHelper.Max(DateTime.Parse("2024-01-01"), DateTime.Parse("2023-02-01")) // 2024-01-01 124 | ``` 125 | 126 | * Random 127 | 128 | Random values: 129 | ```csharp 130 | RandomHelper.Int(1, 100) // 42 131 | RandomHelper.Float() // 0.1337 132 | RandomHelper.Sign() // -1 or 1 133 | RandomHelper.DoubleNormal() // normal distribution around 0.5 limited to 0..1 134 | ``` 135 | Random picks: 136 | ```csharp 137 | RandomHelper.Pick(1, 2, 3, 4, 5) // 4 138 | RandomHelper.PickWeighted(new [] { "a", "test", "blablabla" }, x => x.Length) // likely "blablabla" 139 | new [] { 1, 2, 3, 4, 5 }.PickRandom() // optionally accepts a weight function too 140 | ``` 141 | Shuffle: 142 | ``` 143 | new [] { 1, 2, 3, 4, 5 }.Shuffle() // something like 4, 2, 1, 5, 3 144 | ``` 145 | 146 | * Dictionary 147 | 148 | Value retrieval: 149 | ```csharp 150 | var dict = new Dictionary { ["hello"] = 1, ["world"] = 2 }; 151 | dict.TryGetValue("hello") // 1 152 | dict.TryGetValue("test") // 0 153 | dict.TryGetValue("foo", null, "hello") // 1 154 | dict.TryGetNullableValue("test") // null 155 | ``` 156 | 157 | * Urls 158 | 159 | URL parts combination: 160 | ```csharp 161 | UrlHelper.Combine("http://a.com/foo", "bar/", "/test") // http://a.com/foo/bar/test 162 | ``` 163 | Query generation from an object (accepts anonymous objects, `Dictionary` and `JObject`): 164 | ```csharp 165 | UrlHelper.GetQuery(new { A = 1, B = "hello" }) // "A=1&B=hello" 166 | UrlHelper.GetQuery(new { A = new [] { 1, 2, 3 } }) // "A=1&A=2&A=3" 167 | ``` 168 | 169 | * Tasks (.NET Standard 2.0 only) 170 | 171 | Parallel awaiting (strong typing, up to 7 values): 172 | ```csharp 173 | var (i, str) = await TaskHelper.GetAll( 174 | GetIntAsync(), 175 | GetStringAsync() 176 | ); 177 | ``` 178 | Async ID-based locker: 179 | ```csharp 180 | var locker = new Locker(); 181 | using(await locker.AcquireAsync(123)) 182 | // do stuff 183 | ``` 184 | 185 | * XML 186 | 187 | Attributes: 188 | ```csharp 189 | var xml = XElement.Parse(@""); 190 | xml.Attr("a") // "1337" 191 | xml.ParseAttr("a") // 1337 192 | xml.ParseAttr("b") // true 193 | xml.TryParseAttr("a") // null 194 | ``` 195 | Less verbose serialization with a cleaner resulting XML: 196 | ```csharp 197 | var obj = new MyObject { Foo = "hello", Bar = (int?) null }; 198 | XmlHelper.Serialize(obj) // Hello 199 | XmlHelper.Serialize(obj, clean: false); // lots of junk: xml declaration, namespaces, nil's 200 | XmlHelper.Deserialize("...") // gets it back 201 | ``` -------------------------------------------------------------------------------- /Utils/Linq/ExprHelper.Apply.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Linq.Expressions; 4 | using System.Runtime.CompilerServices; 5 | 6 | namespace Impworks.Utils.Linq; 7 | 8 | public static partial class ExprHelper 9 | { 10 | #region Func 11 | 12 | /// 13 | /// Applies an argument to the expression. 14 | /// 15 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 16 | public static Expression> Apply(Expression> expr, T1 arg) 17 | => ApplyInternal>(expr, arg); 18 | 19 | /// 20 | /// Applies an argument to the expression. 21 | /// 22 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 23 | public static Expression> Apply(Expression> expr, T2 arg) 24 | => ApplyInternal>(expr, arg); 25 | 26 | /// 27 | /// Applies an argument to the expression. 28 | /// 29 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 30 | public static Expression> Apply(Expression> expr, T3 arg) 31 | => ApplyInternal>(expr, arg); 32 | 33 | /// 34 | /// Applies an argument to the expression. 35 | /// 36 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 37 | public static Expression> Apply(Expression> expr, T4 arg) 38 | => ApplyInternal>(expr, arg); 39 | 40 | /// 41 | /// Applies an argument to the expression. 42 | /// 43 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 44 | public static Expression> Apply(Expression> expr, T5 arg) 45 | => ApplyInternal>(expr, arg); 46 | 47 | /// 48 | /// Applies an argument to the expression. 49 | /// 50 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 51 | public static Expression> Apply(Expression> expr, T6 arg) 52 | => ApplyInternal>(expr, arg); 53 | 54 | /// 55 | /// Applies an argument to the expression. 56 | /// 57 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 58 | public static Expression> Apply(Expression> expr, T7 arg) 59 | => ApplyInternal>(expr, arg); 60 | 61 | /// 62 | /// Applies an argument to the expression. 63 | /// 64 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 65 | public static Expression> Apply(Expression> expr, T8 arg) 66 | => ApplyInternal>(expr, arg); 67 | 68 | #endregion 69 | 70 | #region Action 71 | 72 | /// 73 | /// Applies an argument to the expression. 74 | /// 75 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 76 | public static Expression Apply(Expression> expr, T1 arg) 77 | => ApplyInternal(expr, arg); 78 | 79 | /// 80 | /// Applies an argument to the expression. 81 | /// 82 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 83 | public static Expression> Apply(Expression> expr, T2 arg) 84 | => ApplyInternal>(expr, arg); 85 | 86 | /// 87 | /// Applies an argument to the expression. 88 | /// 89 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 90 | public static Expression> Apply(Expression> expr, T3 arg) 91 | => ApplyInternal>(expr, arg); 92 | 93 | /// 94 | /// Applies an argument to the expression. 95 | /// 96 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 97 | public static Expression> Apply(Expression> expr, T4 arg) 98 | => ApplyInternal>(expr, arg); 99 | 100 | /// 101 | /// Applies an argument to the expression. 102 | /// 103 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 104 | public static Expression> Apply(Expression> expr, T5 arg) 105 | => ApplyInternal>(expr, arg); 106 | 107 | /// 108 | /// Applies an argument to the expression. 109 | /// 110 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 111 | public static Expression> Apply(Expression> expr, T6 arg) 112 | => ApplyInternal>(expr, arg); 113 | 114 | /// 115 | /// Applies an argument to the expression. 116 | /// 117 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 118 | public static Expression> Apply(Expression> expr, T7 arg) 119 | => ApplyInternal>(expr, arg); 120 | 121 | /// 122 | /// Applies an argument to the expression. 123 | /// 124 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 125 | public static Expression> Apply(Expression> expr, T8 arg) 126 | => ApplyInternal>(expr, arg); 127 | 128 | #endregion 129 | 130 | #region Implementation 131 | 132 | /// 133 | /// Performs application. 134 | /// 135 | private static Expression ApplyInternal(LambdaExpression lambda, object arg) 136 | { 137 | if(lambda == null) 138 | throw new ArgumentNullException(nameof(lambda)); 139 | 140 | var visitor = new ReplaceParameterByExpressionVisitor(lambda.Parameters.Last(), Expression.Constant(arg)); 141 | return (Expression) visitor.Visit(lambda); 142 | } 143 | 144 | /// 145 | /// Visitor class for replacing a lambda parameter with an expression. 146 | /// 147 | private class ReplaceParameterByExpressionVisitor : ExpressionVisitor 148 | { 149 | public ReplaceParameterByExpressionVisitor(ParameterExpression source, Expression target) 150 | { 151 | _source = source ?? throw new ArgumentNullException(nameof(source)); 152 | _target = target ?? throw new ArgumentNullException(nameof(target)); 153 | 154 | if (_target is ConstantExpression {Value: null}) 155 | _target = Expression.Convert(_target, _source.Type); 156 | } 157 | 158 | private readonly ParameterExpression _source; 159 | private readonly Expression _target; 160 | 161 | protected override Expression VisitLambda(Expression node) => Expression.Lambda(Visit(node.Body), node.Parameters.Where(x => x != _source)); 162 | protected override Expression VisitParameter(ParameterExpression node) => node == _source ? _target : base.VisitParameter(node); 163 | } 164 | 165 | #endregion 166 | } -------------------------------------------------------------------------------- /Utils.Tests/Random/RandomHelper_Values.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Impworks.Utils.Random; 4 | using NUnit.Framework; 5 | 6 | namespace Utils.Tests.Random 7 | { 8 | /// 9 | /// Tests for RandomHelper methods. 10 | /// 11 | [TestFixture] 12 | public class RandomHelper_Values 13 | { 14 | [Test] 15 | public void Float_returns_a_random_value() 16 | { 17 | RandomTestHelper.AtLeastOnce(() => 18 | { 19 | var a = RandomHelper.Float(); 20 | var b = RandomHelper.Float(); 21 | return Math.Abs(a - b) > 0.001; 22 | }); 23 | } 24 | 25 | [Test] 26 | public void Float_returns_a_value_between_0_and_1() 27 | { 28 | RandomTestHelper.Always(() => 29 | { 30 | var value = RandomHelper.Float(); 31 | return value >= 0.0 && value <= 1.0; 32 | }); 33 | } 34 | 35 | [Test] 36 | public void Float_returns_a_value_between_given_values() 37 | { 38 | var min = 5; 39 | var max = 100; 40 | 41 | RandomTestHelper.Always(() => 42 | { 43 | var value = RandomHelper.Float(min, max); 44 | return value >= min && value <= max; 45 | }); 46 | } 47 | 48 | [Test] 49 | public void Double_returns_a_random_value() 50 | { 51 | RandomTestHelper.AtLeastOnce(() => 52 | { 53 | var a = RandomHelper.Double(); 54 | var b = RandomHelper.Double(); 55 | return Math.Abs(a - b) > 0.001; 56 | }); 57 | } 58 | 59 | [Test] 60 | public void Double_returns_a_value_between_0_and_1() 61 | { 62 | RandomTestHelper.Always(() => 63 | { 64 | var value = RandomHelper.Double(); 65 | return value >= 0.0 && value <= 1.0; 66 | }); 67 | } 68 | 69 | [Test] 70 | public void Double_returns_a_value_between_given_values() 71 | { 72 | var min = 13; 73 | var max = 37; 74 | 75 | RandomTestHelper.Always(() => 76 | { 77 | var value = RandomHelper.Double(min, max); 78 | return value >= min && value <= max; 79 | }); 80 | } 81 | 82 | [Test] 83 | public void Int_returns_a_random_value() 84 | { 85 | RandomTestHelper.AtLeastOnce(() => 86 | { 87 | var a = RandomHelper.Int(1, 1000); 88 | var b = RandomHelper.Int(1, 1000); 89 | return a != b; 90 | }); 91 | } 92 | 93 | [Test] 94 | public void Int_returns_a_value_between_given_values() 95 | { 96 | var min = 10; 97 | var max = 500; 98 | 99 | RandomTestHelper.Always(() => 100 | { 101 | var value = RandomHelper.Int(min, max); 102 | return value >= min && value <= max; 103 | }); 104 | } 105 | 106 | [Test] 107 | public void Long_returns_a_random_value() 108 | { 109 | RandomTestHelper.AtLeastOnce(() => 110 | { 111 | var a = RandomHelper.Long(1, 1000); 112 | var b = RandomHelper.Long(1, 1000); 113 | return a != b; 114 | }); 115 | } 116 | 117 | [Test] 118 | public void Long_returns_a_value_between_given_values() 119 | { 120 | var min = 359; 121 | var max = 9001; 122 | 123 | RandomTestHelper.Always(() => 124 | { 125 | var value = RandomHelper.Long(min, max); 126 | return value >= min && value <= max; 127 | }); 128 | } 129 | 130 | [Test] 131 | public void Bool_returns_a_random_value() 132 | { 133 | RandomTestHelper.AtLeastOnce(() => 134 | { 135 | var a = RandomHelper.Bool(); 136 | var b = RandomHelper.Bool(); 137 | return a != b; 138 | }); 139 | } 140 | 141 | [Test] 142 | public void Sign_returns_either_one_or_minus_one() 143 | { 144 | RandomTestHelper.Always(() => 145 | { 146 | var value = RandomHelper.Sign(); 147 | return value == 1 || value == -1; 148 | }); 149 | } 150 | 151 | [Test] 152 | public void Sign_returns_random_values() 153 | { 154 | RandomTestHelper.AtLeastOnce(() => 155 | { 156 | var a = RandomHelper.Sign(); 157 | var b = RandomHelper.Sign(); 158 | return a != b; 159 | }); 160 | } 161 | 162 | [Test] 163 | public void Pick_returns_a_value_from_the_original_collection() 164 | { 165 | var src = Enumerable.Range(1, 100).ToList(); 166 | RandomTestHelper.Always(() => 167 | { 168 | var value = RandomHelper.Pick(src); 169 | return src.Contains(value); 170 | }); 171 | } 172 | 173 | [Test] 174 | public void Pick_returns_different_values() 175 | { 176 | var src = Enumerable.Range(1, 100).ToList(); 177 | RandomTestHelper.AtLeastOnce(() => 178 | { 179 | var a = RandomHelper.Pick(src); 180 | var b = RandomHelper.Pick(src); 181 | return a != b; 182 | }); 183 | } 184 | 185 | [Test] 186 | public void PickWeighted_returns_a_value_from_the_original_collection() 187 | { 188 | var src = Enumerable.Range(1, 100).ToList(); 189 | RandomTestHelper.Always(() => 190 | { 191 | var value = RandomHelper.PickWeighted(src, x => x); 192 | return src.Contains(value); 193 | }); 194 | } 195 | 196 | [Test] 197 | public void PickWeighted_returns_different_values() 198 | { 199 | var src = Enumerable.Range(1, 100).ToList(); 200 | RandomTestHelper.AtLeastOnce(() => 201 | { 202 | var a = RandomHelper.PickWeighted(src, x => x); 203 | var b = RandomHelper.PickWeighted(src, x => x); 204 | return a != b; 205 | }); 206 | } 207 | 208 | [Test] 209 | public void PickWeighted_returns_values_according_to_weight() 210 | { 211 | var numbers = new[] {5, 100}; 212 | var picks = Enumerable.Range(1, 100).Select(x => RandomHelper.PickWeighted(numbers, y => y)) 213 | .GroupBy(x => x) 214 | .ToDictionary(x => x.Key, x => x.Count()); 215 | 216 | Assert.That(picks[100], Is.GreaterThan(picks[5])); 217 | } 218 | 219 | [Test] 220 | public void DoubleNormal_returns_values_in_range() 221 | { 222 | RandomTestHelper.Always(() => RandomHelper.DoubleNormal() is >= 0 and <= 1); 223 | } 224 | 225 | [Test] 226 | public void DoubleNormal_returns_values_around_one_half() 227 | { 228 | var count = 10000; 229 | RandomTestHelper.Always(() => 230 | { 231 | var result = 0.0; 232 | for (var i = 0; i <= count; i++) 233 | result += RandomHelper.DoubleNormal(); 234 | 235 | var avg = result / count; 236 | return avg is >= 0.495 and <= 0.505; 237 | }); 238 | } 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /Utils/Linq/EnumerableExtensions.Destructure.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.CompilerServices; 4 | 5 | namespace Impworks.Utils.Linq; 6 | 7 | #if NETSTANDARD || NET6_0_OR_GREATER 8 | public static partial class EnumerableExtensions 9 | { 10 | #region FirstN 11 | 12 | /// 13 | /// Returns the tuple containing first 2 items of the sequence. 14 | /// Throws an exception if there are not enough elements. 15 | /// 16 | public static ValueTuple First2(this IEnumerable src) 17 | { 18 | using var iter = src.GetEnumerator(); 19 | return ( 20 | iter.MoveNext() ? iter.Current : ThrowTooFew(), 21 | iter.MoveNext() ? iter.Current : ThrowTooFew() 22 | ); 23 | } 24 | 25 | /// 26 | /// Returns the tuple containing first 3 items of the sequence. 27 | /// Throws an exception if there are not enough elements. 28 | /// 29 | public static ValueTuple First3(this IEnumerable src) 30 | { 31 | using var iter = src.GetEnumerator(); 32 | return ( 33 | iter.MoveNext() ? iter.Current : ThrowTooFew(), 34 | iter.MoveNext() ? iter.Current : ThrowTooFew(), 35 | iter.MoveNext() ? iter.Current : ThrowTooFew() 36 | ); 37 | } 38 | 39 | /// 40 | /// Returns the tuple containing first 4 items of the sequence. 41 | /// Throws an exception if there are not enough elements. 42 | /// 43 | public static ValueTuple First4(this IEnumerable src) 44 | { 45 | using var iter = src.GetEnumerator(); 46 | return ( 47 | iter.MoveNext() ? iter.Current : ThrowTooFew(), 48 | iter.MoveNext() ? iter.Current : ThrowTooFew(), 49 | iter.MoveNext() ? iter.Current : ThrowTooFew(), 50 | iter.MoveNext() ? iter.Current : ThrowTooFew() 51 | ); 52 | } 53 | 54 | /// 55 | /// Returns the tuple containing first 5 items of the sequence. 56 | /// Throws an exception if there are not enough elements. 57 | /// 58 | public static ValueTuple First5(this IEnumerable src) 59 | { 60 | using var iter = src.GetEnumerator(); 61 | return ( 62 | iter.MoveNext() ? iter.Current : ThrowTooFew(), 63 | iter.MoveNext() ? iter.Current : ThrowTooFew(), 64 | iter.MoveNext() ? iter.Current : ThrowTooFew(), 65 | iter.MoveNext() ? iter.Current : ThrowTooFew(), 66 | iter.MoveNext() ? iter.Current : ThrowTooFew() 67 | ); 68 | } 69 | 70 | /// 71 | /// Returns the tuple containing first 6 items of the sequence. 72 | /// Throws an exception if there are not enough elements. 73 | /// 74 | public static ValueTuple First6(this IEnumerable src) 75 | { 76 | using var iter = src.GetEnumerator(); 77 | return ( 78 | iter.MoveNext() ? iter.Current : ThrowTooFew(), 79 | iter.MoveNext() ? iter.Current : ThrowTooFew(), 80 | iter.MoveNext() ? iter.Current : ThrowTooFew(), 81 | iter.MoveNext() ? iter.Current : ThrowTooFew(), 82 | iter.MoveNext() ? iter.Current : ThrowTooFew(), 83 | iter.MoveNext() ? iter.Current : ThrowTooFew() 84 | ); 85 | } 86 | 87 | /// 88 | /// Returns the tuple containing first 7 items of the sequence. 89 | /// Throws an exception if there are not enough elements. 90 | /// 91 | public static ValueTuple First7(this IEnumerable src) 92 | { 93 | using var iter = src.GetEnumerator(); 94 | return ( 95 | iter.MoveNext() ? iter.Current : ThrowTooFew(), 96 | iter.MoveNext() ? iter.Current : ThrowTooFew(), 97 | iter.MoveNext() ? iter.Current : ThrowTooFew(), 98 | iter.MoveNext() ? iter.Current : ThrowTooFew(), 99 | iter.MoveNext() ? iter.Current : ThrowTooFew(), 100 | iter.MoveNext() ? iter.Current : ThrowTooFew(), 101 | iter.MoveNext() ? iter.Current : ThrowTooFew() 102 | ); 103 | } 104 | 105 | #endregion 106 | 107 | #region FirstNOrDefault 108 | 109 | /// 110 | /// Returns the tuple containing first 2 items of the sequence. 111 | /// 112 | public static ValueTuple First2OrDefault(this IEnumerable src) 113 | { 114 | using var iter = src.GetEnumerator(); 115 | return ( 116 | iter.MoveNext() ? iter.Current : default, 117 | iter.MoveNext() ? iter.Current : default 118 | ); 119 | } 120 | 121 | /// 122 | /// Returns the tuple containing first 3 items of the sequence. 123 | /// 124 | public static ValueTuple First3OrDefault(this IEnumerable src) 125 | { 126 | using var iter = src.GetEnumerator(); 127 | return ( 128 | iter.MoveNext() ? iter.Current : default, 129 | iter.MoveNext() ? iter.Current : default, 130 | iter.MoveNext() ? iter.Current : default 131 | ); 132 | } 133 | 134 | /// 135 | /// Returns the tuple containing first 4 items of the sequence. 136 | /// 137 | public static ValueTuple First4OrDefault(this IEnumerable src) 138 | { 139 | using var iter = src.GetEnumerator(); 140 | return ( 141 | iter.MoveNext() ? iter.Current : default, 142 | iter.MoveNext() ? iter.Current : default, 143 | iter.MoveNext() ? iter.Current : default, 144 | iter.MoveNext() ? iter.Current : default 145 | ); 146 | } 147 | 148 | /// 149 | /// Returns the tuple containing first 5 items of the sequence. 150 | /// 151 | public static ValueTuple First5OrDefault(this IEnumerable src) 152 | { 153 | using var iter = src.GetEnumerator(); 154 | return ( 155 | iter.MoveNext() ? iter.Current : default, 156 | iter.MoveNext() ? iter.Current : default, 157 | iter.MoveNext() ? iter.Current : default, 158 | iter.MoveNext() ? iter.Current : default, 159 | iter.MoveNext() ? iter.Current : default 160 | ); 161 | } 162 | 163 | /// 164 | /// Returns the tuple containing first 6 items of the sequence. 165 | /// 166 | public static ValueTuple First6OrDefault(this IEnumerable src) 167 | { 168 | using var iter = src.GetEnumerator(); 169 | return ( 170 | iter.MoveNext() ? iter.Current : default, 171 | iter.MoveNext() ? iter.Current : default, 172 | iter.MoveNext() ? iter.Current : default, 173 | iter.MoveNext() ? iter.Current : default, 174 | iter.MoveNext() ? iter.Current : default, 175 | iter.MoveNext() ? iter.Current : default 176 | ); 177 | } 178 | 179 | /// 180 | /// Returns the tuple containing first 7 items of the sequence. 181 | /// 182 | public static ValueTuple First7OrDefault(this IEnumerable src) 183 | { 184 | using var iter = src.GetEnumerator(); 185 | return ( 186 | iter.MoveNext() ? iter.Current : default, 187 | iter.MoveNext() ? iter.Current : default, 188 | iter.MoveNext() ? iter.Current : default, 189 | iter.MoveNext() ? iter.Current : default, 190 | iter.MoveNext() ? iter.Current : default, 191 | iter.MoveNext() ? iter.Current : default, 192 | iter.MoveNext() ? iter.Current : default 193 | ); 194 | } 195 | 196 | #endregion 197 | 198 | #region Helpers 199 | 200 | /// 201 | /// Throws an exception if there are too few elements in the sequence. 202 | /// 203 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 204 | private static T ThrowTooFew() 205 | { 206 | throw new Exception("Sequence contains too few elements"); 207 | } 208 | 209 | #endregion 210 | } 211 | #endif --------------------------------------------------------------------------------