├── .gitignore ├── [Main] ├── Properties │ └── AssemblyInfo.cs ├── Internal │ ├── NullabilityAttributes.cs │ ├── ContractsShim.cs │ ├── ObsoleteMessages.cs │ ├── Grouping.cs │ └── ReSharperAnnotations.cs ├── PropertyInfoExtensions.cs ├── FormattableExtensions.cs ├── AssemblyExtensions.cs ├── DoubleExtensions.cs ├── MethodInfoExtensions.cs ├── DelegateExtensions.cs ├── StringBuilderExtensions.cs ├── ListExtensions.cs ├── CollectionExtensions.cs ├── [Main].csproj ├── DateTimeExtensions.cs ├── DictionaryExtensions.cs ├── TypeExtensions.cs ├── TypeInfoExtensions.cs ├── CustomAttributeProviderExtensions.cs ├── CharExtensions.cs ├── ArrayExtensions.cs └── StringExtensions.cs ├── NuGet.Config ├── ReadMeGenerator ├── App.config ├── Properties │ └── AssemblyInfo.cs ├── ReadMeGenerator.csproj ├── Program.cs └── MarkdownGenerator.cs ├── .editorconfig ├── Tests ├── CompilationTests.cs ├── AssemblyExtensionsTests.cs ├── Tests.csproj ├── TypeExtensionsTests.cs ├── ListExtensionsTests.cs ├── DoubleExtensionsTests.cs ├── DelegateExtensionTests.cs ├── DateTimeExtensionsTests.cs ├── CollectionExtensionsTests.cs ├── StringExtensionsTests.cs ├── StringBuilderExtensionsTests.cs ├── DictionaryExtensionsTests.cs ├── ArrayExtensionsTests.cs └── EnumerableExtensionsTests.cs ├── Tests.Compilation ├── Tests.Compilation.csproj ├── CompilerAssert.cs ├── EnumerableExtensionsTests.cs ├── StringExtensionsTests.cs └── DictionaryExtensionsTests.cs ├── appveyor.yml ├── README.md ├── AshMind.Extensions.sln.DotSettings └── AshMind.Extensions.sln /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | bin/ 3 | obj/ 4 | .vs/ 5 | \#packages/ 6 | *.suo 7 | *.user 8 | *.lock.json 9 | *.nupkg 10 | -------------------------------------------------------------------------------- /[Main]/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | [assembly: AssemblyCopyright("Copyright © Andrey Shchekin 2007–2017")] -------------------------------------------------------------------------------- /NuGet.Config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ReadMeGenerator/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 4 7 | trim_trailing_whitespace = true 8 | insert_final_newline = false 9 | 10 | [*.csproj] 11 | indent_size = 2 -------------------------------------------------------------------------------- /Tests/CompilationTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace AshMind.Extensions.Tests 6 | { 7 | class CompilationTests 8 | { 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /[Main]/Internal/NullabilityAttributes.cs: -------------------------------------------------------------------------------- 1 | #if No_Nullability_Attributes 2 | // ReSharper disable once CheckNamespace 3 | namespace System.Diagnostics.CodeAnalysis 4 | { 5 | internal sealed class MaybeNullAttribute : Attribute {} 6 | } 7 | #endif -------------------------------------------------------------------------------- /[Main]/Internal/ContractsShim.cs: -------------------------------------------------------------------------------- 1 | #if No_Contracts 2 | // ReSharper disable once CheckNamespace 3 | namespace System.Diagnostics.Contracts { 4 | internal class PureAttribute : Attribute {} 5 | internal static class Contract { 6 | [Conditional("_DO_NOT_COMPILE_")] 7 | public static void EndContractBlock() {} 8 | } 9 | } 10 | #endif -------------------------------------------------------------------------------- /[Main]/Internal/ObsoleteMessages.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace AshMind.Extensions.Internal 7 | { 8 | internal static class ObsoleteMessages 9 | { 10 | public const string MethodWillBeRemovedInVersion4StaticConsistency = "This method will be removed in version 4 (most of the code everywhere is using a stitic call, this is too inconsistent)."; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Tests.Compilation/Tests.Compilation.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net4.0;net472;netcoreapp3.0 4 | AshMind.Extensions.Tests.Compilation 5 | AshMind.Extensions.Tests.Compilation 6 | 8 7 | enable 8 | true 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Tests.Compilation/CompilerAssert.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace AshMind.Extensions.Tests.Compilation 7 | { 8 | // The purpose of these is to see there are no unexpected compiler warnings/errors. 9 | // Note: project must be set to WarningAsError. 10 | 11 | public static class CompilerAssert 12 | { 13 | public static void Nullable(ref string? value) 14 | { 15 | } 16 | 17 | public static void NotNullable(object value) 18 | { 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Tests.Compilation/EnumerableExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using AshMind.Extensions; 2 | using System.Collections.Generic; 3 | 4 | namespace AshMind.Extensions.Tests.Compilation 5 | { 6 | public static class EnumerableExtensionsTests 7 | { 8 | public static void EmptyIfNull_AllowsNull() 9 | { 10 | var enumerable = (IEnumerable?)null; 11 | enumerable.EmptyIfNull(); 12 | } 13 | 14 | public static void EmptyIfNull_ReturnsNotNullable() 15 | { 16 | CompilerAssert.NotNullable(new string[0].EmptyIfNull()); 17 | } 18 | 19 | public static void Except_AllowsNullItem() 20 | { 21 | CompilerAssert.NotNullable(new string?[0].Except(null)); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: '{build}' 2 | configuration: Release 3 | image: Visual Studio 2019 Preview 4 | 5 | install: 6 | - ps: |- 7 | choco install dotnetcore-sdk --version 3.0.100-preview7-012821 --pre 8 | 9 | cache: 10 | - C:\Users\appveyor\AppData\Local\Temp\1\chocolatey\vcredist140 -> .appveyor.yml 11 | - C:\Users\appveyor\AppData\Local\Temp\1\chocolatey\dotnetcore-sdk -> .appveyor.yml 12 | 13 | before_build: 14 | - dotnet restore 15 | 16 | build_script: 17 | - dotnet build --configuration Release 18 | 19 | after_test: 20 | - dotnet pack [Main] --configuration Release --output . 21 | 22 | artifacts: 23 | - path: '*.nupkg' 24 | 25 | deploy: 26 | - provider: NuGet 27 | on: 28 | branch: /^(master|\d\.\d)$/ 29 | api_key: 30 | secure: Tz4nbtNMTV00UjCtvQB7yiSC/F8BiSE5Iob9qb8QvO57jrwzFoBXIlcVV+fyTVwE 31 | artifact: /.*\.nupkg/ -------------------------------------------------------------------------------- /Tests.Compilation/StringExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace AshMind.Extensions.Tests.Compilation 4 | { 5 | public static class StringExtensionsTests 6 | { 7 | [Obsolete("To be removed in v4.")] 8 | public static void IsNullOrEmpty_AllowsNull() 9 | { 10 | ((string?)null).IsNullOrEmpty(); 11 | } 12 | 13 | [Obsolete("To be removed in v4.")] 14 | public static void IsNullOrWhiteSpace_AllowsNull() 15 | { 16 | ((string?)null).IsNullOrWhiteSpace(); 17 | } 18 | 19 | public static void NullIfEmpty_AllowsNull() 20 | { 21 | ((string?)null).NullIfEmpty(); 22 | } 23 | 24 | public static void NullIfEmpty_ReturnsNullable() 25 | { 26 | var value = "".NullIfEmpty(); 27 | CompilerAssert.Nullable(ref value); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ReadMeGenerator/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyConfiguration("")] 9 | [assembly: AssemblyCompany("")] 10 | [assembly: AssemblyProduct("ReadMeGenerator")] 11 | [assembly: AssemblyTrademark("")] 12 | 13 | // Setting ComVisible to false makes the types in this assembly not visible 14 | // to COM components. If you need to access a type in this assembly from 15 | // COM, set the ComVisible attribute to true on that type. 16 | [assembly: ComVisible(false)] 17 | 18 | // The following GUID is for the ID of the typelib if this project is exposed to COM 19 | [assembly: Guid("27ca6604-d094-41c0-bca9-8857c656e836")] 20 | -------------------------------------------------------------------------------- /Tests/AssemblyExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using Xunit; 6 | 7 | namespace AshMind.Extensions.Tests { 8 | public class AssemblyExtensionsTests { 9 | [Fact] 10 | public void GetAssemblyFile_ReturnsFileBasedOnAssemblyLocation() { 11 | var assembly = Assembly.GetExecutingAssembly(); 12 | var file = assembly.GetAssemblyFile(); 13 | 14 | Assert.Equal(assembly.Location, file.FullName); 15 | } 16 | 17 | [Fact] 18 | public void GetAssemblyFileFromCodeBase_ReturnsFileBasedOnAssemblyCodeBase() { 19 | var assembly = Assembly.GetExecutingAssembly(); 20 | var file = assembly.GetAssemblyFileFromCodeBase(); 21 | var url = new Uri(assembly.EscapedCodeBase); 22 | 23 | Assert.Equal(url.LocalPath, file.FullName); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /ReadMeGenerator/ReadMeGenerator.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp2.1 4 | ReadMeGenerator 5 | Exe 6 | ReadMeGenerator 7 | false 8 | false 9 | false 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Tests/Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp2.0; net46 4 | AshMind.Extensions.Tests 5 | AshMind.Extensions.Tests 6 | 8 7 | enable 8 | true 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | $(DefineConstants); No_StringBuilder_AppendJoin 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Tests/TypeExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xunit; 3 | 4 | namespace AshMind.Extensions.Tests { 5 | public class TypeExtensionsTests { 6 | #region Test Classes 7 | public interface IInterface {} 8 | public interface ISubInterface : IInterface { } 9 | public class ClassWithInterface : IInterface {} 10 | public class SubclassOfClassWithInterface : ClassWithInterface { } 11 | public class ClassWithSubInterface : ISubInterface { } 12 | public class ClassWithoutInterface {} 13 | #endregion 14 | 15 | [Theory] 16 | [InlineData(typeof(ClassWithInterface), true)] 17 | [InlineData(typeof(SubclassOfClassWithInterface), true)] 18 | [InlineData(typeof(ClassWithSubInterface), true)] 19 | [InlineData(typeof(ISubInterface), true)] 20 | [InlineData(typeof(IInterface), false)] 21 | [InlineData(typeof(ClassWithoutInterface), false)] 22 | public void HasInterface(Type type, bool expectedResult) { 23 | Assert.Equal(expectedResult, type.HasInterface()); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /[Main]/Internal/Grouping.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using JetBrains.Annotations; 6 | 7 | namespace AshMind.Extensions.Internal { 8 | internal sealed class Grouping : IGrouping { 9 | public TKey Key { get; } 10 | [NotNull] private readonly IEnumerable _elements; 11 | 12 | public Grouping(TKey key, [NotNull] IEnumerable elements) { 13 | Key = key; 14 | _elements = elements; 15 | } 16 | 17 | public static IGrouping Create(TKey key, [NotNull] IEnumerable elements) { 18 | return new Grouping(key, elements); 19 | } 20 | 21 | public IEnumerator GetEnumerator() { 22 | return _elements.GetEnumerator(); 23 | } 24 | 25 | #region IEnumerable Members 26 | 27 | IEnumerator IEnumerable.GetEnumerator() { 28 | return _elements.GetEnumerator(); 29 | } 30 | 31 | #endregion 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /[Main]/PropertyInfoExtensions.cs: -------------------------------------------------------------------------------- 1 | #if No_Property_SetValue_NoIndex 2 | using System; 3 | using System.Diagnostics.Contracts; 4 | using System.Reflection; 5 | using JetBrains.Annotations; 6 | using Contracts = System.Diagnostics.Contracts; 7 | using PureAttribute = JetBrains.Annotations.PureAttribute; 8 | 9 | namespace AshMind.Extensions { 10 | /// 11 | /// Provides a set of extension methods for operations on . 12 | /// 13 | public static class PropertyInfoExtensions { 14 | public static void SetValue([NotNull] this PropertyInfo property, [CanBeNull] object obj, [CanBeNull] object value) { 15 | if (property == null) throw new ArgumentNullException("property"); 16 | Contract.EndContractBlock(); 17 | 18 | property.SetValue(obj, value, null); 19 | } 20 | 21 | [Contracts.Pure] [Pure] [CanBeNull] 22 | public static object GetValue([NotNull] this PropertyInfo property, [CanBeNull] object obj) { 23 | if (property == null) throw new ArgumentNullException("property"); 24 | Contract.EndContractBlock(); 25 | 26 | return property.GetValue(obj, null); 27 | } 28 | } 29 | } 30 | #endif 31 | -------------------------------------------------------------------------------- /ReadMeGenerator/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Reflection; 6 | 7 | namespace AshMind.Extensions.ReadMeGenerator { 8 | public static class Program { 9 | public static void Main(string[] args) { 10 | try { 11 | var readmePath = FindPathToReadMe(); 12 | using (var stream = File.OpenWrite(readmePath)) 13 | using (var writer = new StreamWriter(stream)) { 14 | new MarkdownGenerator().WriteTo(writer); 15 | } 16 | } 17 | catch (Exception ex) { 18 | FluentConsole.Red.Text(ex); 19 | } 20 | } 21 | 22 | private static string FindPathToReadMe() { 23 | var initialDirectory = new FileInfo(typeof(Program).GetTypeInfo().Assembly.Location).Directory; 24 | 25 | var directory = initialDirectory; 26 | while (!directory.EnumerateFiles("*.sln").Any()) { 27 | directory = directory.Parent; 28 | if (directory == null) 29 | throw new Exception("Could not find solution folder for path '" + initialDirectory.FullName + "'."); 30 | } 31 | 32 | return Path.Combine(directory.FullName, "README.md"); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /[Main]/FormattableExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using JetBrains.Annotations; 4 | using Contracts = System.Diagnostics.Contracts; 5 | 6 | namespace AshMind.Extensions { 7 | /// 8 | /// Provides a set of extension methods for operations on IFormattable{T}. 9 | /// 10 | public static class FormattableExtensions { 11 | /// 12 | /// Formats the value using the specified format provider. 13 | /// 14 | /// The value to be formatted. 15 | /// The provider to use to format the value. 16 | /// The in the specified format. 17 | [Contracts.Pure] [Pure] 18 | public static string ToString([NotNull] this IFormattable value, IFormatProvider provider) { 19 | return value.ToString(null, provider); 20 | } 21 | 22 | /// 23 | /// Formats the value using the invariant culture format provider. 24 | /// 25 | /// The value to be formatted. 26 | /// The , formatted using invariant culture format provider. 27 | [Contracts.Pure] [Pure] 28 | public static string ToInvariantString([NotNull] this IFormattable value) { 29 | return value.ToString(CultureInfo.InvariantCulture); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Tests/ListExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | using Xunit; 7 | 8 | #pragma warning disable xUnit1019 // https://github.com/xunit/xunit/issues/1897 9 | 10 | namespace AshMind.Extensions.Tests { 11 | public class ListExtensionsTests { 12 | public delegate IList ListFactory(params int[] values); 13 | 14 | [Theory] 15 | [MemberData(nameof(Lists))] 16 | public void InsertRange_WorksCorrectly(Expression factory) { 17 | var list = factory.Compile()(0, 1, 2, 3, 4); 18 | list.InsertRange(3, new[] { 21, 22, 23 }); 19 | 20 | Assert.Equal( 21 | new[] { 0, 1, 2, 21, 22, 23, 3, 4 }, 22 | list.ToArray() 23 | ); 24 | } 25 | 26 | [Theory] 27 | [MemberData(nameof(Lists))] 28 | public void RemoveRange_WorksCorrectly(Expression factory) { 29 | var list = factory.Compile()(0, 1, 2, 3, 4); 30 | list.RemoveRange(1, 3); 31 | 32 | Assert.Equal(new[] { 0, 4 }, list.ToArray()); 33 | } 34 | 35 | public static IEnumerable Lists { 36 | get { 37 | Func, object[]> adapt = f => new object[] { f }; 38 | 39 | yield return adapt(xs => new List(xs)); 40 | yield return adapt(xs => new Collection(xs.ToList())); 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Tests/DoubleExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Xunit; 5 | using Xunit.Extensions; 6 | 7 | namespace AshMind.Extensions.Tests { 8 | public class DoubleExtensionsTests { 9 | [Theory] 10 | [InlineData(0)] 11 | [InlineData(Double.NaN)] 12 | [Obsolete("To be removed in v4.")] 13 | public void IsNaN_IsEqualTo_Double_IsNaN(double value) { 14 | Assert.Equal(value.IsNaN(), Double.IsNaN(value)); 15 | } 16 | 17 | [Theory] 18 | [InlineData(0)] 19 | [InlineData(Double.PositiveInfinity)] 20 | [InlineData(Double.NegativeInfinity)] 21 | [Obsolete("To be removed in v4.")] 22 | public void IsInfinity_IsEqual_ToDoubleIsInfinity(double value) { 23 | Assert.Equal(value.IsInfinity(), Double.IsInfinity(value)); 24 | } 25 | 26 | [Theory] 27 | [InlineData(0)] 28 | [InlineData(Double.PositiveInfinity)] 29 | [InlineData(Double.NegativeInfinity)] 30 | [Obsolete("To be removed in v4.")] 31 | public void IsPositiveInfinity_IsEqual_ToDoubleIsPositiveInfinity(double value) { 32 | Assert.Equal(value.IsPositiveInfinity(), Double.IsPositiveInfinity(value)); 33 | } 34 | 35 | [Theory] 36 | [InlineData(0)] 37 | [InlineData(Double.PositiveInfinity)] 38 | [InlineData(Double.NegativeInfinity)] 39 | [Obsolete("To be removed in v4.")] 40 | public void IsNegativeInfinity_IsEqual_ToDoubleIsNegativeInfinity(double value) { 41 | Assert.Equal(value.IsNegativeInfinity(), Double.IsNegativeInfinity(value)); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build status](https://ci.appveyor.com/api/projects/status/jg5841626qcwpc6b)](https://ci.appveyor.com/project/ashmind/ashmind-extensions) 2 | 3 | A set of very conservative extension methods — most of those closely follow common naming and implementation patterns in the .NET framework. You can get it from NuGet as [AshMind.Extensions](https://www.nuget.org/packages/AshMind.Extensions/). 4 | 5 | Below is an auto-generated list of the methods provided: 6 | ### Array 7 | 1. IndexOf 8 | 2. LastIndexOf 9 | 3. Reverse 10 | 4. Sort 11 | 12 | ### Assembly 13 | 1. GetAssemblyFile 14 | 2. GetAssemblyFileFromCodeBase 15 | 16 | ### Char 17 | 18 | ### Collection 19 | 1. AddRange 20 | 2. RemoveAll 21 | 3. RemoveWhere 22 | 23 | ### CustomAttributeProvider 24 | 1. GetCustomAttribute 25 | 2. GetCustomAttributes 26 | 3. IsDefined 27 | 28 | ### DateTime 29 | 1. TruncateToHours 30 | 2. TruncateToMilliseconds 31 | 3. TruncateToMinutes 32 | 4. TruncateToSeconds 33 | 34 | ### Delegate 35 | 1. AsComparison 36 | 2. AsFunc 37 | 3. AsPredicate 38 | 4. ToComparer 39 | 40 | ### Dictionary 41 | 1. AsReadOnlyDictionary 42 | 2. GetOrAdd 43 | 3. GetValueOrDefault 44 | 45 | ### Double 46 | 47 | ### Enumerable 48 | 1. Any 49 | 2. AsCollection 50 | 3. AsList 51 | 4. AsReadOnlyCollection 52 | 5. AsReadOnlyList 53 | 6. Concat 54 | 7. EmptyIfNull 55 | 8. Except 56 | 9. GroupAdjacentBy 57 | 10. HavingMax 58 | 11. HavingMin 59 | 60 | ### Formattable 61 | 1. ToInvariantString 62 | 2. ToString 63 | 64 | ### List 65 | 1. EnumerateRange 66 | 2. InsertRange 67 | 3. RemoveRange 68 | 69 | ### MethodInfo 70 | 1. CreateDelegate 71 | 72 | ### StringBuilder 73 | 74 | ### String 75 | 1. Contains 76 | 2. NullIfEmpty 77 | 3. RemoveEnd 78 | 4. RemoveStart 79 | 5. Split 80 | 6. SubstringAfter 81 | 7. SubstringAfterLast 82 | 8. SubstringBefore 83 | 9. SubstringBeforeLast 84 | 10. TruncateEnd 85 | 86 | ### Type 87 | 1. HasInterface 88 | 2. IsAssignableFrom 89 | 3. IsAssignableTo 90 | 4. IsGenericTypeDefinedAs 91 | 5. IsSameAsOrSubclassOf 92 | 93 | -------------------------------------------------------------------------------- /ReadMeGenerator/MarkdownGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Reflection; 6 | using JetBrains.Annotations; 7 | 8 | namespace AshMind.Extensions.ReadMeGenerator { 9 | public class MarkdownGenerator { 10 | public void WriteTo([NotNull] TextWriter writer) { 11 | writer.WriteLine("[![Build status](https://ci.appveyor.com/api/projects/status/jg5841626qcwpc6b)](https://ci.appveyor.com/project/ashmind/ashmind-extensions)"); 12 | writer.WriteLine(); 13 | writer.WriteLine("A set of very conservative extension methods — most of those closely follow common naming and implementation patterns in the .NET framework. You can get it from NuGet as [AshMind.Extensions](https://www.nuget.org/packages/AshMind.Extensions/)."); 14 | writer.WriteLine(""); 15 | writer.WriteLine("Below is an auto-generated list of the methods provided:"); 16 | 17 | var extensionsAssembly = typeof(EnumerableExtensions).GetTypeInfo().Assembly; 18 | var extensionTypes = extensionsAssembly.GetExportedTypes(); 19 | 20 | foreach (var type in extensionTypes.OrderBy(t => t.Name)) { 21 | if (type.GetTypeInfo().IsDefined(typeof(ObsoleteAttribute))) 22 | continue; 23 | 24 | writer.WriteLine("### {0}", type.Name.RemoveEnd("Extensions")); 25 | 26 | var methodNames = type.GetMethods(BindingFlags.Static | BindingFlags.Public) 27 | .Where(m => !m.IsDefined(typeof(ObsoleteAttribute))) 28 | .Select(m => m.Name) 29 | .Distinct() 30 | .OrderBy(n => n); 31 | 32 | var index = 1; 33 | foreach (var methodName in methodNames) { 34 | writer.Write(" {0}. {1}", index, methodName); 35 | writer.WriteLine(); 36 | index += 1; 37 | } 38 | 39 | writer.WriteLine(); 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Tests.Compilation/DictionaryExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using AshMind.Extensions; 2 | using System.Collections.Generic; 3 | 4 | namespace AshMind.Extensions.Tests.Compilation 5 | { 6 | public static class DictionaryExtensionsTests 7 | { 8 | public static void GetValueOrDefault_ReturnsNullableValue_IfDictionaryTValueIsNullable() 9 | { 10 | var dictionary = new Dictionary(); 11 | var value = dictionary.GetValueOrDefault("x"); 12 | 13 | CompilerAssert.Nullable(ref value); 14 | } 15 | 16 | public static void GetValueOrDefault_ReturnsNullableValue_IfDictionaryTValueIsNotNullable() 17 | { 18 | var dictionary = new Dictionary(); 19 | var value = dictionary.GetValueOrDefault("x"); 20 | 21 | CompilerAssert.Nullable(ref value); 22 | } 23 | 24 | public static void GetValueOrDefault_ReturnsNullableValue_IfDictionaryTValueIsNullable_ForIDictionary() 25 | { 26 | var dictionary = (IDictionary)new Dictionary(); 27 | var value = dictionary.GetValueOrDefault("x"); 28 | 29 | CompilerAssert.Nullable(ref value); 30 | } 31 | 32 | public static void GetValueOrDefault_ReturnsNullableValue_IfDictionaryTValueIsNotNullable_ForIDictionary() 33 | { 34 | var dictionary = (IDictionary)new Dictionary(); 35 | var value = dictionary.GetValueOrDefault("x"); 36 | 37 | CompilerAssert.Nullable(ref value); 38 | } 39 | 40 | #if !NET40 41 | public static void GetValueOrDefault_ReturnsNullableValue_IfDictionaryTValueIsNullable_ForIReadOnlyDictionary() 42 | { 43 | var dictionary = (IReadOnlyDictionary)new Dictionary(); 44 | var value = dictionary.GetValueOrDefault("x"); 45 | 46 | CompilerAssert.Nullable(ref value); 47 | } 48 | 49 | public static void GetValueOrDefault_ReturnsNullableValue_IfDictionaryTValueIsNotNullable_ForIReadOnlyDictionary() 50 | { 51 | var dictionary = (IReadOnlyDictionary)new Dictionary(); 52 | var value = dictionary.GetValueOrDefault("x"); 53 | 54 | CompilerAssert.Nullable(ref value); 55 | } 56 | #endif 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Tests/DelegateExtensionTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Xunit; 5 | 6 | namespace AshMind.Extensions.Tests { 7 | public class DelegateExtensionTests { 8 | [Fact] 9 | public void Function_AsPredicate() { 10 | Func func = x => x == "test"; 11 | var predicate = func.AsPredicate(); 12 | 13 | Assert.Same(func.Target, predicate.Target); 14 | Assert.Same(func.Method, predicate.Method); 15 | } 16 | 17 | [Fact] 18 | public void Predicate_AsFunc() { 19 | Predicate predicate = x => x == "test"; 20 | var func = predicate.AsFunc(); 21 | 22 | Assert.Same(predicate.Target, func.Target); 23 | Assert.Same(predicate.Method, func.Method); 24 | } 25 | 26 | [Fact] 27 | public void Function_AsComparison() { 28 | Func func = (x, y) => x.CompareTo(y); 29 | var comparison = func.AsComparison(); 30 | 31 | Assert.Same(func.Target, comparison.Target); 32 | Assert.Same(func.Method, comparison.Method); 33 | } 34 | 35 | [Fact] 36 | public void Comparison_AsFunc() { 37 | Comparison comparison = (x, y) => x.CompareTo(y); 38 | var func = comparison.AsFunc(); 39 | 40 | Assert.Same(comparison.Target, func.Target); 41 | Assert.Same(comparison.Method, func.Method); 42 | } 43 | 44 | [Fact] 45 | public void Comparison_ToComparer() { 46 | Comparison comparison = (x, y) => x.CompareTo(y); 47 | var comparer = comparison.ToComparer(); 48 | 49 | Assert.Equal(comparison("a", "b"), comparer.Compare("a", "b")); 50 | Assert.Equal(comparison("b", "a"), comparer.Compare("b", "a")); 51 | Assert.Equal(comparison("a", "a"), comparer.Compare("a", "a")); 52 | } 53 | 54 | [Fact] 55 | public void Function_ToComparer() { 56 | Func func = (x, y) => x.CompareTo(y); 57 | var comparer = func.ToComparer(); 58 | 59 | Assert.Equal(func("a", "b"), comparer.Compare("a", "b")); 60 | Assert.Equal(func("b", "a"), comparer.Compare("b", "a")); 61 | Assert.Equal(func("a", "a"), comparer.Compare("a", "a")); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /[Main]/AssemblyExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.Contracts; 3 | #if !No_FileInfo 4 | using System.IO; 5 | #endif 6 | using System.Reflection; 7 | using JetBrains.Annotations; 8 | using Contracts = System.Diagnostics.Contracts; 9 | using PureAttribute = JetBrains.Annotations.PureAttribute; 10 | 11 | namespace AshMind.Extensions { 12 | /// Provides a set of extension methods for operations on . 13 | public static class AssemblyExtensions { 14 | #if !No_FileInfo 15 | /// Gets a object based on . 16 | /// An object providing the location. 17 | /// A object located at . 18 | /// The assembly is a dynamic assembly. 19 | [Contracts.Pure] [Pure] [NotNull] 20 | public static FileInfo GetAssemblyFile([NotNull] this Assembly assembly) { 21 | if (assembly == null) throw new ArgumentNullException(nameof(assembly)); 22 | Contract.EndContractBlock(); 23 | 24 | return new FileInfo(assembly.Location); 25 | } 26 | 27 | /// Gets a object based on . 28 | /// An object providing the code base. 29 | /// 30 | /// A object located at . 31 | /// 32 | /// The assembly is a dynamic assembly. 33 | /// The does not contain a file:// URL. 34 | [Contracts.Pure] [Pure] [NotNull] 35 | public static FileInfo GetAssemblyFileFromCodeBase([NotNull] this Assembly assembly) { 36 | if (assembly == null) throw new ArgumentNullException(nameof(assembly)); 37 | Contract.EndContractBlock(); 38 | 39 | var uri = new Uri(assembly.EscapedCodeBase); 40 | if (!uri.IsFile) 41 | throw new NotSupportedException("GetAssemblyFileFromCodeBase is only supported if CodeBase uses a file:// schema."); 42 | 43 | return new FileInfo(uri.LocalPath); 44 | } 45 | #endif 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Tests/DateTimeExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Xunit; 5 | 6 | // ReSharper disable PossibleNullReferenceException 7 | 8 | namespace AshMind.Extensions.Tests { 9 | public class DateTimeExtensionsTests { 10 | [Fact] 11 | public void TruncateToMilliseconds_ReturnsSameTimeTruncatedToMilliseconds_ForDateTimeOffset() { 12 | var date = new DateTimeOffset(2000, 10, 10, 10, 10, 10, 10, TimeSpan.FromHours(10)); 13 | Assert.Equal(date, date.AddMilliseconds(0.3).TruncateToMilliseconds()); 14 | } 15 | 16 | [Fact] 17 | public void TruncateToSeconds_ReturnsSameTimeTruncatedToSeconds_ForDateTimeOffset() { 18 | var date = new DateTimeOffset(2000, 10, 10, 10, 10, 10, TimeSpan.FromHours(10)); 19 | Assert.Equal(date, date.AddSeconds(0.55).TruncateToSeconds()); 20 | } 21 | 22 | [Fact] 23 | public void TruncateToMinutes_ReturnsSameTimeTruncatedToMinutes_ForDateTimeOffset() { 24 | var date = new DateTimeOffset(2000, 10, 10, 10, 10, 0, TimeSpan.FromHours(10)); 25 | Assert.Equal(date, date.AddSeconds(35.5).TruncateToMinutes()); 26 | } 27 | 28 | [Fact] 29 | public void TruncateToHours_ReturnsSameTimeTruncatedToHours_ForDateTimeOffset() { 30 | var date = new DateTimeOffset(2000, 10, 10, 10, 0, 0, TimeSpan.FromHours(10)); 31 | Assert.Equal(date, date.AddMinutes(35.55).TruncateToHours()); 32 | } 33 | 34 | [Fact] 35 | public void TruncateToMilliseconds_ReturnsSameTimeTruncatedToMilliseconds_ForDateTime() { 36 | var date = new DateTime(2000, 10, 10, 10, 10, 10, 10); 37 | Assert.Equal(date, date.AddMilliseconds(0.3).TruncateToMilliseconds()); 38 | } 39 | 40 | [Fact] 41 | public void TruncateToSeconds_ReturnsSameTimeTruncatedToSeconds_ForDateTime() { 42 | var date = new DateTime(2000, 10, 10, 10, 10, 10); 43 | Assert.Equal(date, date.AddSeconds(0.55).TruncateToSeconds()); 44 | } 45 | 46 | [Fact] 47 | public void TruncateToMinutes_ReturnsSameTimeTruncatedToMinutes_ForDateTime() { 48 | var date = new DateTime(2000, 10, 10, 10, 10, 0); 49 | Assert.Equal(date, date.AddSeconds(35.5).TruncateToMinutes()); 50 | } 51 | 52 | [Fact] 53 | public void TruncateToHours_ReturnsSameTimeTruncatedToHours_ForDateTime() { 54 | var date = new DateTime(2000, 10, 10, 10, 0, 0); 55 | Assert.Equal(date, date.AddMinutes(35.55).TruncateToHours()); 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /AshMind.Extensions.sln.DotSettings: -------------------------------------------------------------------------------- 1 | 2 | DO_NOT_SHOW 3 | DO_NOT_SHOW 4 | OPTIMISTIC 5 | END_OF_LINE 6 | END_OF_LINE 7 | END_OF_LINE 8 | END_OF_LINE 9 | END_OF_LINE 10 | END_OF_LINE 11 | END_OF_LINE 12 | END_OF_LINE 13 | System 14 | System.Collections.Generic 15 | System.Linq 16 | True 17 | True 18 | True -------------------------------------------------------------------------------- /Tests/CollectionExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | using Xunit; 7 | 8 | #pragma warning disable xUnit1019 // https://github.com/xunit/xunit/issues/1897 9 | 10 | namespace AshMind.Extensions.Tests { 11 | public class CollectionExtensionsTests { 12 | public delegate ICollection CollectionFactory(params int[] values); 13 | 14 | [Fact] 15 | public void RemoveWhere_ForList_ProvidesCorrectIndex() { 16 | var list = new List {0, 1, 2, 3}; 17 | list.RemoveWhere((item, index) => { 18 | Assert.Equal(item, index); 19 | return false; 20 | }); 21 | } 22 | 23 | [Theory] 24 | [MemberData(nameof(Collections))] 25 | public void RemoveWhere_WithIndex_RemovesFrom(Expression factory) { 26 | var collection = factory.Compile()(1, 2, 3, 4, 5); 27 | collection.RemoveWhere((item, index) => item == 2); 28 | Assert.Equal(new[] { 1, 3, 4, 5 }, collection.ToArray()); 29 | } 30 | 31 | [Theory] 32 | [MemberData(nameof(Collections))] 33 | public void RemoveWhere_RemovesFrom(Expression factory) { 34 | var collection = factory.Compile()(1, 2, 3, 4, 5); 35 | collection.RemoveWhere(item => item == 2); 36 | Assert.Equal(new[] { 1, 3, 4, 5 }, collection.ToArray()); 37 | } 38 | 39 | [Theory] 40 | [MemberData(nameof(Collections))] 41 | public void RemoveWhere_ReturnsCorrectCount(Expression factory) { 42 | var collection = factory.Compile()(1, 2, 3, 4, 5); 43 | var count = collection.RemoveWhere(item => item > 2); 44 | 45 | Assert.Equal(3, count); 46 | } 47 | 48 | [Theory] 49 | [MemberData(nameof(Collections))] 50 | public void RemoveWhere_WithIndex_ReturnsCorrectCount(Expression factory) { 51 | var collection = factory.Compile()(1, 2, 3, 4, 5); 52 | var count = collection.RemoveWhere(item => item > 2); 53 | 54 | Assert.Equal(3, count); 55 | } 56 | 57 | public static IEnumerable Collections { 58 | get { 59 | Func, object[]> adapt = f => new object[] {f}; 60 | 61 | yield return adapt(xs => new List(xs)); 62 | yield return adapt(xs => new Collection(xs.ToList())); 63 | yield return adapt(xs => new HashSet(xs)); 64 | yield return adapt(xs => new SortedSet(xs)); 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /[Main]/DoubleExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using JetBrains.Annotations; 3 | using Contracts = System.Diagnostics.Contracts; 4 | 5 | using static AshMind.Extensions.Internal.ObsoleteMessages; 6 | 7 | namespace AshMind.Extensions { 8 | /// 9 | /// Provides a set of extension methods for operations on Double. 10 | /// 11 | public static class DoubleExtensions { 12 | /// Returns a value indicating whether the specified number evaluates to a value that is not a number (). 13 | /// A value. 14 | /// true if evaluates to ; otherwise, false. 15 | /// 16 | [Contracts.Pure] [Pure] 17 | [Obsolete(MethodWillBeRemovedInVersion4StaticConsistency)] 18 | public static bool IsNaN(this double value) { 19 | return Double.IsNaN(value); 20 | } 21 | 22 | /// 23 | /// Returns a value indicating whether the specified number evaluates to positive or negative infinity. 24 | /// 25 | /// A value. 26 | /// 27 | /// true if evaluates to or ; otherwise, false. 28 | /// 29 | /// 30 | [Contracts.Pure] [Pure] 31 | [Obsolete(MethodWillBeRemovedInVersion4StaticConsistency)] 32 | public static bool IsInfinity(this double value) { 33 | return Double.IsInfinity(value); 34 | } 35 | 36 | /// 37 | /// Returns a value indicating whether the specified number evaluates to positive infinity. 38 | /// 39 | /// A value. 40 | /// 41 | /// true if evaluates to ; otherwise, false. 42 | /// 43 | /// 44 | [Contracts.Pure] [Pure] 45 | [Obsolete(MethodWillBeRemovedInVersion4StaticConsistency)] 46 | public static bool IsPositiveInfinity(this double value) { 47 | return Double.IsPositiveInfinity(value); 48 | } 49 | 50 | /// 51 | /// Returns a value indicating whether the specified number evaluates to negative infinity. 52 | /// 53 | /// A value. 54 | /// 55 | /// true if evaluates to ; otherwise, false. 56 | /// 57 | /// 58 | [Contracts.Pure] [Pure] 59 | [Obsolete(MethodWillBeRemovedInVersion4StaticConsistency)] 60 | public static bool IsNegativeInfinity(this double value) { 61 | return Double.IsNegativeInfinity(value); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Tests/StringExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Xunit; 5 | using Xunit.Extensions; 6 | 7 | namespace AshMind.Extensions.Tests { 8 | public class StringExtensionsTests { 9 | [Theory] 10 | [InlineData(null, null)] 11 | [InlineData("", null)] 12 | [InlineData("abc", "abc")] 13 | public void NullIfEmpty(string value, string expectedResult) { 14 | Assert.Equal(expectedResult, value.NullIfEmpty()); 15 | } 16 | 17 | [Theory] 18 | [InlineData("ab18ba18", "18", "ab")] 19 | [InlineData("abcdefgh", "x", "abcdefgh")] 20 | [InlineData("abcdefgh", "a", "")] 21 | [InlineData("abcdefgh", "fg", "abcde")] 22 | public void SubstringBefore(string value, string delimiter, string expectedResult) { 23 | Assert.Equal(expectedResult, value.SubstringBefore(delimiter)); 24 | } 25 | 26 | [Theory] 27 | [InlineData("ab18ba18", "18", "ab18ba")] 28 | [InlineData("abcdefgh", "x", "abcdefgh")] 29 | [InlineData("abcdefgh", "a", "")] 30 | [InlineData("abcdefgh", "fg", "abcde")] 31 | public void SubstringBeforeLast(string value, string delimiter, string expectedResult) { 32 | Assert.Equal(expectedResult, value.SubstringBeforeLast(delimiter)); 33 | } 34 | 35 | [Theory] 36 | [InlineData("abcdabcd", "bc", "dabcd")] 37 | [InlineData("abcdefgh", "x", "abcdefgh")] 38 | [InlineData("abcdefgh", "h", "")] 39 | [InlineData("abcdefgh", "cd", "efgh")] 40 | [InlineData("a.b.c.d", "a.b.d", "a.b.c.d")] 41 | public void SubstringAfter(string value, string delimiter, string expectedResult) { 42 | Assert.Equal(expectedResult, value.SubstringAfter(delimiter)); 43 | } 44 | 45 | [Theory] 46 | [InlineData("abcdabcd", "bc", "d")] 47 | [InlineData("abcdefgh", "x", "abcdefgh")] 48 | [InlineData("abcdefgh", "h", "")] 49 | [InlineData("abcdefgh", "cd", "efgh")] 50 | [InlineData("a.b.c.d", "a.b.d", "a.b.c.d")] 51 | public void SubstringAfterLast(string value, string delimiter, string expectedResult) { 52 | Assert.Equal(expectedResult, value.SubstringAfterLast(delimiter)); 53 | } 54 | 55 | [Theory] 56 | [InlineData("abcdabcd", "ab", "cdabcd")] 57 | [InlineData("abcdabcd", "xy", "abcdabcd")] 58 | [InlineData("abcdabcd", "abcdabcd", "")] 59 | public void RemoveStart(string value, string prefix, string expectedResult) { 60 | Assert.Equal(expectedResult, value.RemoveStart(prefix)); 61 | } 62 | 63 | 64 | [Theory] 65 | [InlineData("abcdabcd", "cd", "abcdab")] 66 | [InlineData("abcdabcd", "xy", "abcdabcd")] 67 | [InlineData("abcdabcd", "abcdabcd", "")] 68 | public void RemoveEnd(string value, string suffix, string expectedResult) { 69 | Assert.Equal(expectedResult, value.RemoveEnd(suffix)); 70 | } 71 | 72 | [Theory] 73 | [InlineData("a", 0, "")] 74 | [InlineData("a", 2, "a")] 75 | [InlineData("ab", 2, "ab")] 76 | [InlineData("abcde", 2, "ab")] 77 | public void TruncateEnd(string value, int maxLength, string expectedResult) { 78 | Assert.Equal(expectedResult, value.TruncateEnd(maxLength)); 79 | } 80 | 81 | [Theory] 82 | [InlineData("a", 0, "$", "")] 83 | [InlineData("a", 2, "$", "a")] 84 | [InlineData("ab", 2, "$", "ab")] 85 | [InlineData("abcde", 2, "$", "a$")] 86 | [InlineData("abcd", 3, "1234", "123")] 87 | [InlineData("abcde", 3, "123", "123")] 88 | [InlineData("abcde", 4, "123", "a123")] 89 | public void TruncateEnd_WithSuffix(string value, int maxLength, string suffix, string expectedResult) { 90 | Assert.Equal(expectedResult, value.TruncateEnd(maxLength, suffix)); 91 | } 92 | 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /Tests/StringBuilderExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Xunit; 6 | 7 | namespace AshMind.Extensions.Tests { 8 | public class StringBuilderExtensionsTests { 9 | #if No_StringBuilder_AppendJoin 10 | [Theory] 11 | [InlineData(",", new[] { 1, 2 }, "[1,2]")] 12 | [InlineData(null, new[] { 1, 2 }, "[12]")] 13 | [InlineData(",", new int[0], "[]")] 14 | [InlineData(null, new int[0], "[]")] 15 | [InlineData(",", new[] { null, "x", null, "y", null }, "[,x,,y,]")] 16 | [InlineData(" s ", new[] { "x", "y" }, "[x s y]")] 17 | [InlineData(",", new[] { "x", "y", "z" }, "[x,y,z]")] 18 | public void AppendJoin_WorksCorrectly_WithEnumerable(string separator, Array values, string expectedResult) { 19 | var builder = new StringBuilder("[").AppendJoin(separator, values.Cast()).Append("]"); 20 | Assert.Equal(expectedResult, builder.ToString()); 21 | } 22 | 23 | [Theory] 24 | [InlineData(",", new[] { 1, 2 }, "[1,2]")] 25 | [InlineData(" s ", new[] { 1, 2 }, "[1 s 2]")] 26 | [InlineData(",", new[] { 1, 2, 3 }, "[1,2,3]")] 27 | [InlineData(null, new[] { 1, 2 }, "[12]")] 28 | [InlineData(",", new int[0], "[]")] 29 | [InlineData(null, new int[0], "[]")] 30 | public void AppendJoin_WorksCorrectly_WithArray(string separator, int[] values, string expectedResult) { 31 | var builder = new StringBuilder("[").AppendJoin(separator, values).Append("]"); 32 | Assert.Equal(expectedResult, builder.ToString()); 33 | } 34 | 35 | [Theory] 36 | [InlineData(",", new[] { "a", "b" }, "[a,b]")] 37 | [InlineData(null, new[] { "a", "b" }, "[ab]")] 38 | [InlineData(",", new string[0], "[]")] 39 | [InlineData(null, new string[0], "[]")] 40 | [InlineData(",", new[] { null, "x", null, "y", null }, "[,x,,y,]")] 41 | [InlineData(" s ", new[] { "x", "y" }, "[x s y]")] 42 | [InlineData(",", new[] { "x", "y", "z" }, "[x,y,z]")] 43 | public void AppendJoin_WorksCorrectly_WithStringArray(string separator, string[] values, string expectedResult) { 44 | var builder = new StringBuilder("[").AppendJoin(separator, values).Append("]"); 45 | Assert.Equal(expectedResult, builder.ToString()); 46 | } 47 | 48 | [Fact] 49 | public void AppendJoin_ThrowsArgumentNullException_IfBuilderIsNull() { 50 | Assert.Throws( 51 | // ReSharper disable once AssignNullToNotNullAttribute 52 | () => ((StringBuilder?)null)!.AppendJoin(",", new[] {1, 2}) 53 | ); 54 | } 55 | 56 | [Fact] 57 | public void AppendJoin_ThrowsArgumentNullException_IfBuilderIsNullForStringParamsOverload() { 58 | Assert.Throws( 59 | // ReSharper disable once AssignNullToNotNullAttribute 60 | () => ((StringBuilder?)null)!.AppendJoin(",", "a", "b") 61 | ); 62 | } 63 | 64 | [Fact] 65 | public void AppendJoin_ThrowsArgumentNullException_IfItemsAreNull() { 66 | Assert.Throws( 67 | // ReSharper disable once AssignNullToNotNullAttribute 68 | () => new StringBuilder().AppendJoin(",", null!) 69 | ); 70 | } 71 | 72 | [Fact] 73 | public void AppendJoin_ThrowsArgumentNullException_IfItemsAreNullForStringParamsOverload() { 74 | Assert.Throws( 75 | // ReSharper disable once AssignNullToNotNullAttribute 76 | () => new StringBuilder().AppendJoin(",", null!) 77 | ); 78 | } 79 | 80 | private IEnumerable Enumerate(T[] values) { 81 | foreach (var item in values) { 82 | yield return item; 83 | } 84 | } 85 | #endif 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Tests/DictionaryExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Xunit; 3 | 4 | namespace AshMind.Extensions.Tests 5 | { 6 | public class DictionaryExtensionsTests { 7 | [Fact] 8 | public void GetValueOrDefault_GetsDefault_IfValueIsNotPresent_ForReferenceType() { 9 | var dictionary = new Dictionary(); 10 | Assert.Null(dictionary.GetValueOrDefault("key")); 11 | } 12 | 13 | [Fact] 14 | public void GetValueOrDefault_GetsDefault_IfValueIsNotPresent_ForValueType() { 15 | var dictionary = new Dictionary(); 16 | Assert.Equal(0, dictionary.GetValueOrDefault("key")); 17 | } 18 | 19 | [Fact] 20 | public void GetValueOrDefault_GetsValue_IfValueIsPresent() { 21 | var dictionary = new Dictionary { 22 | { "key", "value" } 23 | }; 24 | Assert.Equal("value", dictionary.GetValueOrDefault("key")); 25 | } 26 | 27 | [Fact] 28 | public void GetValueOrDefault_Compiles_ForEachType() { 29 | var dictionary = new Dictionary(); 30 | 31 | // ReSharper disable ReturnValueOfPureMethodIsNotUsed 32 | // ReSharper disable RedundantCast 33 | dictionary.GetValueOrDefault("x"); 34 | ((IDictionary)dictionary).GetValueOrDefault("x"); 35 | ((IReadOnlyDictionary)dictionary).GetValueOrDefault("x"); 36 | // ReSharper restore RedundantCast 37 | // ReSharper restore ReturnValueOfPureMethodIsNotUsed 38 | } 39 | 40 | [Fact] 41 | public void GetOrAdd_WithValue_WhenKeyIsPresent_ReturnsExistingValue() { 42 | var dictionary = new Dictionary {{ "key", "existing" }}; 43 | var value = dictionary.GetOrAdd("key", "new"); 44 | 45 | Assert.Equal("existing", value); 46 | } 47 | 48 | [Fact] 49 | public void GetOrAdd_WithValue_WhenKeyIsNotPresent_ReturnsNewValue() { 50 | var dictionary = new Dictionary(); 51 | var value = dictionary.GetOrAdd("key", "new"); 52 | 53 | Assert.Equal("new", value); 54 | } 55 | 56 | [Fact] 57 | public void GetOrAdd_WithValue_WhenKeyIsNotPresent_AddsNewValue() { 58 | var dictionary = new Dictionary(); 59 | dictionary.GetOrAdd("key", "new"); 60 | 61 | Assert.True(dictionary.ContainsKey("key")); 62 | Assert.Equal("new", dictionary["key"]); 63 | } 64 | 65 | [Fact] 66 | public void GetOrAdd_WithFunction_WhenKeyIsPresent_ReturnsExistingValue() { 67 | var dictionary = new Dictionary { { "key", "existing" } }; 68 | var value = dictionary.GetOrAdd("key", _ => "new"); 69 | 70 | Assert.Equal("existing", value); 71 | } 72 | 73 | [Fact] 74 | public void GetOrAdd_WithFunction_WhenKeyIsNotPresent_ReturnsNewValue() { 75 | var dictionary = new Dictionary(); 76 | var value = dictionary.GetOrAdd("key", _ => "new"); 77 | 78 | Assert.Equal("new", value); 79 | } 80 | 81 | [Fact] 82 | public void GetOrAdd_WithFunction_WhenKeyIsNotPresent_AddsNewValue() { 83 | var dictionary = new Dictionary(); 84 | dictionary.GetOrAdd("key", _ => "new"); 85 | 86 | Assert.True(dictionary.ContainsKey("key")); 87 | Assert.Equal("new", dictionary["key"]); 88 | } 89 | 90 | [Fact] 91 | public void GetOrAdd_WithFunction_WhenKeyIsNotPresent_CallsFunctionWithCorrectKey() { 92 | var dictionary = new Dictionary(); 93 | string? key = null; 94 | dictionary.GetOrAdd("key", k => key = k); 95 | 96 | Assert.Equal("key", key); 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /[Main]/MethodInfoExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.Contracts; 3 | using System.Reflection; 4 | using JetBrains.Annotations; 5 | using Contracts = System.Diagnostics.Contracts; 6 | using PureAttribute = JetBrains.Annotations.PureAttribute; 7 | 8 | namespace AshMind.Extensions { 9 | /// 10 | /// Provides a set of extension methods for operations on . 11 | /// 12 | public static class MethodInfoExtensions { 13 | #if No_MethodInfo_CreateDelegate 14 | /// 15 | /// Creates a delegate of the specified type from a specified method. 16 | /// 17 | /// The method to create the delegate for. 18 | /// The type of the delegate to create. 19 | /// The delegate for . 20 | /// is null. 21 | [Contracts.Pure] [Pure] [NotNull] 22 | public static Delegate CreateDelegate([NotNull] this MethodInfo method, [NotNull] Type delegateType) { 23 | if (method == null) throw new ArgumentNullException("method"); 24 | Contract.EndContractBlock(); 25 | 26 | return Delegate.CreateDelegate(delegateType, method); 27 | } 28 | 29 | /// 30 | /// Creates a delegate of the specified type with the specified target from a specified method. 31 | /// 32 | /// The method to create the delegate for. 33 | /// The type of the delegate to create. 34 | /// The object targeted by the delegate. 35 | /// The delegate for . 36 | /// is null. 37 | [Contracts.Pure] [Pure] [NotNull] 38 | public static Delegate CreateDelegate([NotNull] this MethodInfo method, [NotNull] Type delegateType, object target) { 39 | if (method == null) throw new ArgumentNullException("method"); 40 | Contract.EndContractBlock(); 41 | 42 | return Delegate.CreateDelegate(delegateType, target, method); 43 | } 44 | #endif 45 | 46 | /// 47 | /// Creates a delegate of the specified type from a specified method. 48 | /// 49 | /// The type of the delegate to create. 50 | /// The method to create the delegate for. 51 | /// The delegate for . 52 | /// is null. 53 | [Contracts.Pure] [Pure] [NotNull] 54 | public static TDelegate CreateDelegate([NotNull] this MethodInfo method) { 55 | if (method == null) throw new ArgumentNullException(nameof(method)); 56 | Contract.EndContractBlock(); 57 | 58 | return (TDelegate)(object)method.CreateDelegate(typeof(TDelegate)); 59 | } 60 | 61 | /// 62 | /// Creates a delegate of the specified type with the specified target from a specified method. 63 | /// 64 | /// The type of the delegate to create. 65 | /// The method to create the delegate for. 66 | /// The object targeted by the delegate. 67 | /// The delegate for . 68 | /// is null. 69 | [Contracts.Pure] [Pure] [NotNull] 70 | public static TDelegate CreateDelegate([NotNull] this MethodInfo method, object target) { 71 | if (method == null) throw new ArgumentNullException(nameof(method)); 72 | Contract.EndContractBlock(); 73 | 74 | return (TDelegate)(object)method.CreateDelegate(typeof(TDelegate), target); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /[Main]/DelegateExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | using JetBrains.Annotations; 5 | using Contracts = System.Diagnostics.Contracts; 6 | 7 | namespace AshMind.Extensions { 8 | /// 9 | /// Provides a set of extension methods for operations on delegates. 10 | /// 11 | public static class DelegateExtensions { 12 | #region DelegateBasedComparer Class 13 | 14 | private class DelegateBasedComparer : IComparer { 15 | [NotNull] private readonly Comparison comparison; 16 | 17 | public DelegateBasedComparer([NotNull] Comparison comparison) { 18 | this.comparison = comparison; 19 | } 20 | 21 | public int Compare(T x, T y) { 22 | return comparison(x, y); 23 | } 24 | } 25 | 26 | #endregion 27 | 28 | /// 29 | /// Converts Func{T, bool} into a . 30 | /// 31 | /// A function to convert. 32 | /// Predicate{T} identical to the original function. 33 | [Contracts.Pure] [Pure] [NotNull] 34 | public static Predicate AsPredicate([NotNull] this Func function) { 35 | return As>(function); 36 | } 37 | 38 | /// 39 | /// Converts into a Func{T, bool}. 40 | /// 41 | /// A predicate to convert. 42 | /// Func{T, bool} identical to the original predicate. 43 | [Contracts.Pure] [Pure] [NotNull] 44 | public static Func AsFunc([NotNull] this Predicate predicate) { 45 | return As>(predicate); 46 | } 47 | 48 | /// 49 | /// Converts Func{T, T, int} into a . 50 | /// 51 | /// A function to convert. 52 | /// identical to the original function. 53 | [Contracts.Pure] [Pure] [NotNull] 54 | public static Comparison AsComparison([NotNull] this Func function) { 55 | return As>(function); 56 | } 57 | 58 | /// 59 | /// Converts into a Func{T, T, int}. 60 | /// 61 | /// A comparison to convert. 62 | /// Func{T, T, int} identical to the original comparison. 63 | [Contracts.Pure] [Pure] [NotNull] 64 | public static Func AsFunc([NotNull] this Comparison comparison) { 65 | return As>(comparison); 66 | } 67 | 68 | #if No_MethodInfo_CreateDelegate 69 | [Contracts.Pure] [NotNull] 70 | private static TDelegate As([NotNull] Delegate @delegate) { 71 | return (TDelegate)(object)Delegate.CreateDelegate(typeof(TDelegate), @delegate.Target, @delegate.Method); 72 | } 73 | #else 74 | [Contracts.Pure] [NotNull] 75 | private static TDelegate As([NotNull] Delegate @delegate) { 76 | var method = @delegate.GetMethodInfo(); 77 | if (method == null) 78 | throw new ArgumentException("Delegate does not have a method info.", nameof(@delegate)); 79 | 80 | return (TDelegate)(object)method.CreateDelegate(typeof(TDelegate), @delegate.Target); 81 | } 82 | #endif 83 | 84 | /// 85 | /// Converts into an . 86 | /// 87 | /// A comparison to convert. 88 | /// that acts identical to the original comparison. 89 | [Contracts.Pure] [Pure] [NotNull] 90 | public static IComparer ToComparer([NotNull] this Comparison comparison) { 91 | return new DelegateBasedComparer(comparison); 92 | } 93 | 94 | /// 95 | /// Converts Func{T, T, int} into an . 96 | /// 97 | /// A function to convert. 98 | /// that acts identical to the original function. 99 | [Contracts.Pure] [Pure] [NotNull] 100 | public static IComparer ToComparer([NotNull] this Func function) { 101 | return new DelegateBasedComparer(function.AsComparison()); 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /Tests/ArrayExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | using Xunit; 7 | 8 | #pragma warning disable xUnit1019 // https://github.com/xunit/xunit/issues/1897 9 | 10 | namespace AshMind.Extensions.Tests { 11 | public class ArrayExtensionsTests { 12 | [Theory] 13 | [MemberData(nameof(ActionMethods))] 14 | public void TestAction(Expression> extensionMethod, Expression> builtInMethod) { 15 | var arrayForExtensionMethod = new[] { 5, 4, 3, 2, 1, 2, 3, 4, 5 }; 16 | var arrayForBuiltInMethod = arrayForExtensionMethod.ToArray(); 17 | 18 | extensionMethod.Compile().Invoke(arrayForExtensionMethod); 19 | builtInMethod.Compile().Invoke(arrayForBuiltInMethod); 20 | 21 | Assert.Equal(arrayForBuiltInMethod, arrayForExtensionMethod); 22 | } 23 | 24 | [Theory] 25 | [MemberData(nameof(FunctionMethods))] 26 | public void TestFunction(Expression> extensionMethod, Expression> builtInMethod) { 27 | var array = new[] { 5, 4, 3, 2, 1, 2, 3, 4, 5 }; 28 | 29 | var builtInResult = builtInMethod.Compile().Invoke(array); 30 | var extensionResult = extensionMethod.Compile().Invoke(array); 31 | 32 | Assert.Equal(builtInResult, extensionResult); 33 | } 34 | 35 | public static IEnumerable ActionMethods { 36 | get { return GetMethods>(m => m.ReturnType == typeof(void)); } 37 | } 38 | 39 | public static IEnumerable FunctionMethods { 40 | get { return GetMethods>(m => m.ReturnType != typeof(void)); } 41 | } 42 | 43 | private static IEnumerable GetMethods(Func filter) { 44 | var extensionMethods = typeof(ArrayExtensions).GetMethods(BindingFlags.Public | BindingFlags.Static).Where(filter); 45 | var arrayMethods = typeof(Array).GetMethods(); 46 | foreach (var method in extensionMethods) { 47 | var arrayMethod = arrayMethods.Where(m => Matches(m, method)) 48 | .OrderByDescending(m => m.IsGenericMethodDefinition ? 1 : 0) 49 | .First(); 50 | 51 | yield return new object[] { MakeCall(method), MakeCall(arrayMethod) }; 52 | } 53 | } 54 | 55 | private static Expression MakeCall(MethodInfo method) { 56 | if (method.IsGenericMethodDefinition) 57 | method = method.MakeGenericMethod(typeof(int)); 58 | 59 | var arrayParameter = Expression.Parameter(typeof(int[]), "array"); 60 | var body = (Expression)Expression.Call(null, method, GuessArguments(method, arrayParameter)); 61 | if (body.Type.IsValueType && body.Type != typeof(void)) 62 | body = Expression.Convert(body, typeof(object)); 63 | 64 | return Expression.Lambda(body, arrayParameter); 65 | } 66 | 67 | private static IEnumerable GuessArguments(MethodInfo method, ParameterExpression arrayParameter) { 68 | yield return arrayParameter; 69 | foreach (var parameter in method.GetParameters().Skip(1)) { 70 | yield return GuessArgumentValue(parameter); 71 | } 72 | } 73 | 74 | private static Expression GuessArgumentValue(ParameterInfo parameter) { 75 | if (parameter.Name.EndsWith("index", StringComparison.InvariantCultureIgnoreCase)) 76 | return Expression.Constant(4); 77 | 78 | if (parameter.Name == "count" || parameter.Name == "length") 79 | return Expression.Constant(3); 80 | 81 | if (parameter.Name == "value") 82 | return Expression.Constant(4); 83 | 84 | if (parameter.ParameterType == typeof(Comparison)) 85 | return Expression.Constant((Comparison) ((a, b) => a.CompareTo(b))); 86 | 87 | if (parameter.ParameterType == typeof(IComparer)) 88 | return Expression.Constant(Comparer.Default, parameter.ParameterType); 89 | 90 | return Expression.Constant(null, parameter.ParameterType); 91 | } 92 | 93 | private static bool Matches(MethodInfo method, MethodInfo other) { 94 | return method.Name == other.Name 95 | && method.ReturnType == other.ReturnType 96 | && Enumerable.SequenceEqual( 97 | method.GetParameters().Select(p => p.Name), 98 | other.GetParameters().Select(p => p.Name) 99 | ); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /AshMind.Extensions.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29201.188 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "[Main]", "[Main]\[Main].csproj", "{3539722D-F21A-4909-8513-1156D3462859}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests", "Tests\Tests.csproj", "{F75E18C8-6A81-419A-A52C-B9C9592473B2}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReadMeGenerator", "ReadMeGenerator\ReadMeGenerator.csproj", "{27CA6604-D094-41C0-BCA9-8857C656E836}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.Compilation", "Tests.Compilation\Tests.Compilation.csproj", "{3BCEFC39-5A30-496D-94C7-914A47FD3BFF}" 13 | EndProject 14 | Global 15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 16 | Debug|Any CPU = Debug|Any CPU 17 | Debug|Mixed Platforms = Debug|Mixed Platforms 18 | Debug|x86 = Debug|x86 19 | Release|Any CPU = Release|Any CPU 20 | Release|Mixed Platforms = Release|Mixed Platforms 21 | Release|x86 = Release|x86 22 | EndGlobalSection 23 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 24 | {3539722D-F21A-4909-8513-1156D3462859}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {3539722D-F21A-4909-8513-1156D3462859}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {3539722D-F21A-4909-8513-1156D3462859}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU 27 | {3539722D-F21A-4909-8513-1156D3462859}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU 28 | {3539722D-F21A-4909-8513-1156D3462859}.Debug|x86.ActiveCfg = Debug|Any CPU 29 | {3539722D-F21A-4909-8513-1156D3462859}.Debug|x86.Build.0 = Debug|Any CPU 30 | {3539722D-F21A-4909-8513-1156D3462859}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {3539722D-F21A-4909-8513-1156D3462859}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {3539722D-F21A-4909-8513-1156D3462859}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU 33 | {3539722D-F21A-4909-8513-1156D3462859}.Release|Mixed Platforms.Build.0 = Release|Any CPU 34 | {3539722D-F21A-4909-8513-1156D3462859}.Release|x86.ActiveCfg = Release|Any CPU 35 | {3539722D-F21A-4909-8513-1156D3462859}.Release|x86.Build.0 = Release|Any CPU 36 | {F75E18C8-6A81-419A-A52C-B9C9592473B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {F75E18C8-6A81-419A-A52C-B9C9592473B2}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {F75E18C8-6A81-419A-A52C-B9C9592473B2}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU 39 | {F75E18C8-6A81-419A-A52C-B9C9592473B2}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU 40 | {F75E18C8-6A81-419A-A52C-B9C9592473B2}.Debug|x86.ActiveCfg = Debug|Any CPU 41 | {F75E18C8-6A81-419A-A52C-B9C9592473B2}.Debug|x86.Build.0 = Debug|Any CPU 42 | {F75E18C8-6A81-419A-A52C-B9C9592473B2}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {F75E18C8-6A81-419A-A52C-B9C9592473B2}.Release|Any CPU.Build.0 = Release|Any CPU 44 | {F75E18C8-6A81-419A-A52C-B9C9592473B2}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU 45 | {F75E18C8-6A81-419A-A52C-B9C9592473B2}.Release|Mixed Platforms.Build.0 = Release|Any CPU 46 | {F75E18C8-6A81-419A-A52C-B9C9592473B2}.Release|x86.ActiveCfg = Release|Any CPU 47 | {F75E18C8-6A81-419A-A52C-B9C9592473B2}.Release|x86.Build.0 = Release|Any CPU 48 | {27CA6604-D094-41C0-BCA9-8857C656E836}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 49 | {27CA6604-D094-41C0-BCA9-8857C656E836}.Debug|Any CPU.Build.0 = Debug|Any CPU 50 | {27CA6604-D094-41C0-BCA9-8857C656E836}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU 51 | {27CA6604-D094-41C0-BCA9-8857C656E836}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU 52 | {27CA6604-D094-41C0-BCA9-8857C656E836}.Debug|x86.ActiveCfg = Debug|Any CPU 53 | {27CA6604-D094-41C0-BCA9-8857C656E836}.Debug|x86.Build.0 = Debug|Any CPU 54 | {27CA6604-D094-41C0-BCA9-8857C656E836}.Release|Any CPU.ActiveCfg = Release|Any CPU 55 | {27CA6604-D094-41C0-BCA9-8857C656E836}.Release|Any CPU.Build.0 = Release|Any CPU 56 | {27CA6604-D094-41C0-BCA9-8857C656E836}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU 57 | {27CA6604-D094-41C0-BCA9-8857C656E836}.Release|Mixed Platforms.Build.0 = Release|Any CPU 58 | {27CA6604-D094-41C0-BCA9-8857C656E836}.Release|x86.ActiveCfg = Release|Any CPU 59 | {27CA6604-D094-41C0-BCA9-8857C656E836}.Release|x86.Build.0 = Release|Any CPU 60 | {3BCEFC39-5A30-496D-94C7-914A47FD3BFF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 61 | {3BCEFC39-5A30-496D-94C7-914A47FD3BFF}.Debug|Any CPU.Build.0 = Debug|Any CPU 62 | {3BCEFC39-5A30-496D-94C7-914A47FD3BFF}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU 63 | {3BCEFC39-5A30-496D-94C7-914A47FD3BFF}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU 64 | {3BCEFC39-5A30-496D-94C7-914A47FD3BFF}.Debug|x86.ActiveCfg = Debug|Any CPU 65 | {3BCEFC39-5A30-496D-94C7-914A47FD3BFF}.Debug|x86.Build.0 = Debug|Any CPU 66 | {3BCEFC39-5A30-496D-94C7-914A47FD3BFF}.Release|Any CPU.ActiveCfg = Release|Any CPU 67 | {3BCEFC39-5A30-496D-94C7-914A47FD3BFF}.Release|Any CPU.Build.0 = Release|Any CPU 68 | {3BCEFC39-5A30-496D-94C7-914A47FD3BFF}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU 69 | {3BCEFC39-5A30-496D-94C7-914A47FD3BFF}.Release|Mixed Platforms.Build.0 = Release|Any CPU 70 | {3BCEFC39-5A30-496D-94C7-914A47FD3BFF}.Release|x86.ActiveCfg = Release|Any CPU 71 | {3BCEFC39-5A30-496D-94C7-914A47FD3BFF}.Release|x86.Build.0 = Release|Any CPU 72 | EndGlobalSection 73 | GlobalSection(SolutionProperties) = preSolution 74 | HideSolutionNode = FALSE 75 | EndGlobalSection 76 | GlobalSection(ExtensibilityGlobals) = postSolution 77 | SolutionGuid = {90E81F81-0072-4454-8A38-73E975048C6E} 78 | EndGlobalSection 79 | EndGlobal 80 | -------------------------------------------------------------------------------- /[Main]/StringBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.Contracts; 4 | using System.Text; 5 | using JetBrains.Annotations; 6 | 7 | namespace AshMind.Extensions { 8 | /// 9 | /// Provides a set of extension methods for operations on StringBuilder. 10 | /// 11 | public static class StringBuilderExtensions { 12 | #if No_StringBuilder_AppendJoin 13 | /// 14 | /// Appends all the elements of a string array, using the specified separator between each member. 15 | /// 16 | /// The to append to. 17 | /// 18 | /// The string to use as a separator. is included in the returned string only if has more than one element. 19 | /// 20 | /// A array that contains the elements to concatenate. 21 | /// A reference to after the append operation has completed. 22 | /// 23 | /// is null; or, is null 24 | /// 25 | /// 26 | /// If is null, an empty string () is used instead. 27 | /// If any member of is null, an empty string is used instead. 28 | /// 29 | public static StringBuilder AppendJoin([NotNull] this StringBuilder builder, [CanBeNull] string? separator, [NotNull, ItemCanBeNull] params string?[] values) { 30 | if (builder == null) throw new ArgumentNullException(nameof(builder)); 31 | if (values == null) throw new ArgumentNullException(nameof(values)); 32 | Contract.EndContractBlock(); 33 | 34 | var length = values.Length; 35 | if (length == 0) 36 | return builder; 37 | 38 | builder.Append(values[0]); 39 | for (var i = 1; i < length; i++) { 40 | builder.Append(separator).Append(values[i]); 41 | } 42 | return builder; 43 | } 44 | 45 | /// 46 | /// Appends the members of a collection, using the specified separator between each member. 47 | /// 48 | /// The type of the members of . 49 | /// The to append to. 50 | /// 51 | /// The string to use as a separator. is included in the returned string only if has more than one element. 52 | /// 53 | /// A collection that contains the objects to concatenate. 54 | /// A reference to after the append operation has completed. 55 | /// 56 | /// is null; or, is null 57 | /// 58 | /// 59 | /// If is null, an empty string () is used instead. 60 | /// If any member of is null, an empty string is used instead. 61 | /// 62 | public static StringBuilder AppendJoin([NotNull] this StringBuilder builder, [CanBeNull] string? separator, [NotNull, ItemCanBeNull] IEnumerable values) { 63 | if (builder == null) throw new ArgumentNullException(nameof(builder)); 64 | if (values == null) throw new ArgumentNullException(nameof(values)); 65 | Contract.EndContractBlock(); 66 | 67 | var array = values as T[]; 68 | if (array != null) { 69 | var length = array.Length; 70 | if (length == 0) 71 | return builder; 72 | 73 | var item = array[0]; 74 | if (item != null) { 75 | // ReSharper disable once RedundantToStringCallForValueType (avoiding boxing) 76 | builder.Append(item.ToString()); 77 | } 78 | for (var i = 1; i < length; i++) { 79 | item = array[i]; 80 | builder.Append(separator); 81 | if (item != null) { 82 | // ReSharper disable once RedundantToStringCallForValueType (avoiding boxing) 83 | builder.Append(item.ToString()); 84 | } 85 | } 86 | return builder; 87 | } 88 | 89 | using (var enumerator = values.GetEnumerator()) { 90 | if (!enumerator.MoveNext()) 91 | return builder; 92 | 93 | var current = enumerator.Current; 94 | if (current != null) { 95 | // ReSharper disable once RedundantToStringCallForValueType (avoiding boxing) 96 | builder.Append(current.ToString()); 97 | } 98 | 99 | while (enumerator.MoveNext()) { 100 | builder.Append(separator); 101 | current = enumerator.Current; 102 | if (current != null) { 103 | // ReSharper disable once RedundantToStringCallForValueType (avoiding boxing) 104 | builder.Append(current.ToString()); 105 | } 106 | } 107 | } 108 | return builder; 109 | } 110 | #endif 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /[Main]/ListExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.Contracts; 4 | using JetBrains.Annotations; 5 | using Contracts = System.Diagnostics.Contracts; 6 | using PureAttribute = JetBrains.Annotations.PureAttribute; 7 | 8 | namespace AshMind.Extensions 9 | { 10 | /// 11 | /// Provides a set of extension methods for operations on . 12 | /// 13 | public static class ListExtensions { 14 | /// 15 | /// Inserts the elements of a collection into the at the specified index. 16 | /// 17 | /// The type of the elements of . 18 | /// The list to which new elements will be inserted. 19 | /// The zero-based index at which the new elements should be inserted. 20 | /// 21 | /// The collection whose elements should be inserted into the . The collection itself cannot 22 | /// be a null reference (Nothing in Visual Basic), but it can contain elements that are a null 23 | /// reference (Nothing in Visual Basic), if type is a reference type. 24 | /// 25 | public static void InsertRange([NotNull] this IList list, int index, [NotNull] IEnumerable collection) { 26 | var concreteList = list as List; 27 | if (concreteList != null) { 28 | concreteList.InsertRange(index, collection); 29 | return; 30 | } 31 | 32 | var currentIndex = index; 33 | foreach (var item in collection) { 34 | list.Insert(currentIndex, item); 35 | currentIndex += 1; 36 | } 37 | } 38 | 39 | /// 40 | /// Removes a range of elements from the . 41 | /// 42 | /// The type of the elements of . 43 | /// The list to remove range from. 44 | /// The zero-based starting index of the range of elements to remove. 45 | /// The number of elements to remove. 46 | public static void RemoveRange([NotNull] this IList list, int index, int count) { 47 | var concreteList = list as List; 48 | if (concreteList != null) { 49 | concreteList.RemoveRange(index, count); 50 | return; 51 | } 52 | 53 | for (var offset = count - 1; offset >= 0; offset--) { 54 | list.RemoveAt(index + offset); 55 | } 56 | } 57 | 58 | /// 59 | /// Produces a limited range of elements from the . 60 | /// 61 | /// The type of the elements of . 62 | /// The list to produce range from. 63 | /// The zero-based element index at which the range starts. 64 | /// The number of elements in the range. 65 | /// 66 | /// An containing all elements from the specified range. 67 | /// 68 | [Contracts.Pure] [Pure] [NotNull] 69 | public static IEnumerable EnumerateRange([NotNull] this IList list, int index, int count) { 70 | if (list == null) throw new ArgumentNullException(nameof(list)); 71 | Contract.EndContractBlock(); 72 | 73 | for (var i = index; i < index + count; i++) { 74 | yield return list[i]; 75 | } 76 | } 77 | 78 | #if !No_ReadOnlyCollections 79 | /// 80 | /// Produces a limited range of elements from the . 81 | /// 82 | /// The type of the elements of . 83 | /// The list to produce range from. 84 | /// The zero-based element index at which the range starts. 85 | /// The number of elements in the range. 86 | /// 87 | /// An containing all elements from the specified range. 88 | /// 89 | [Contracts.Pure] [Pure] [NotNull] 90 | public static IEnumerable EnumerateRange([NotNull] this IReadOnlyList list, int index, int count) { 91 | if (list == null) throw new ArgumentNullException(nameof(list)); 92 | Contract.EndContractBlock(); 93 | for (var i = index; i < index + count; i++) { 94 | yield return list[i]; 95 | } 96 | } 97 | 98 | /// 99 | /// Produces a limited range of elements from the . . 100 | /// 101 | /// The type of the elements of . 102 | /// The list to produce range from. 103 | /// The zero-based element index at which the range starts. 104 | /// The number of elements in the range. 105 | /// 106 | /// An containing all elements from the specified range. 107 | /// 108 | [Contracts.Pure] [Pure] [NotNull] 109 | public static IEnumerable EnumerateRange([NotNull] this List list, int index, int count) { 110 | if (list == null) throw new ArgumentNullException(nameof(list)); 111 | Contract.EndContractBlock(); 112 | return ((IReadOnlyList)list).EnumerateRange(index, count); 113 | } 114 | #endif 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /[Main]/CollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.Contracts; 4 | using JetBrains.Annotations; 5 | 6 | namespace AshMind.Extensions { 7 | /// 8 | /// Provides a set of extension methods for operations on . 9 | /// 10 | public static class CollectionExtensions { 11 | /// 12 | /// Adds the specified elements to the end of the collection. 13 | /// 14 | /// The type of the elements in the . 15 | /// The collection to which the elements should be added. 16 | /// The elements to add to . 17 | public static void AddRange([NotNull] this ICollection collection, [NotNull] IEnumerable values) { 18 | if (collection == null) throw new ArgumentNullException(nameof(collection)); 19 | if (values == null) throw new ArgumentNullException(nameof(values)); 20 | Contract.EndContractBlock(); 21 | 22 | var list = collection as List; 23 | if (list != null) { 24 | list.AddRange(values); 25 | return; 26 | } 27 | 28 | var set = collection as ISet; 29 | if (set != null) { 30 | set.UnionWith(values); 31 | return; 32 | } 33 | 34 | foreach (var item in values) { 35 | collection.Add(item); 36 | } 37 | } 38 | 39 | /// 40 | /// Removes all occurrences of the specified elements from . 41 | /// 42 | /// The type of the elements in the . 43 | /// The collection from which to remove elements. 44 | /// The elements to remove from . 45 | public static void RemoveAll([NotNull] this ICollection collection, [NotNull] IEnumerable values) { 46 | if (collection == null) throw new ArgumentNullException(nameof(collection)); 47 | if (values == null) throw new ArgumentNullException(nameof(values)); 48 | Contract.EndContractBlock(); 49 | 50 | foreach (var value in values) { 51 | collection.Remove(value); 52 | } 53 | } 54 | 55 | /// 56 | /// Removes all elements that match the conditions defined by the specified predicate from the collection. 57 | /// 58 | /// 59 | /// The collection from which to remove elements. 60 | /// 61 | /// 62 | /// The Func<T, bool> delegate that defines the conditions of the elements to remove. 63 | /// 64 | /// The number of elements that were removed from the collection. 65 | public static int RemoveWhere([NotNull] this ICollection collection, [NotNull] [InstantHandle] Func predicate) { 66 | var concreteList = collection as List; 67 | if (concreteList != null) 68 | return concreteList.RemoveAll(predicate.AsPredicate()); 69 | 70 | var list = collection as IList; 71 | if (list != null) 72 | return RemoveFromListWhere(list, (item, index) => predicate(item)); 73 | 74 | var set = collection as HashSet; 75 | if (set != null) 76 | return set.RemoveWhere(predicate.AsPredicate()); 77 | 78 | var sortedSet = collection as SortedSet; 79 | if (sortedSet != null) 80 | return sortedSet.RemoveWhere(predicate.AsPredicate()); 81 | 82 | var itemsToRemove = new List(); 83 | foreach (var item in collection) { 84 | if (predicate(item)) 85 | itemsToRemove.Add(item); 86 | } 87 | 88 | collection.RemoveAll(itemsToRemove); 89 | return itemsToRemove.Count; 90 | } 91 | 92 | /// 93 | /// Removes all elements that match the conditions defined by the specified predicate from the collection. 94 | /// 95 | /// 96 | /// The collection from which to remove items. 97 | /// 98 | /// 99 | /// The Func<T, int, bool> delegate that defines the conditions of the elements to remove; 100 | /// the second parameter of the delegate represents the index of the element. 101 | /// 102 | /// The number of elements that were removed from the collection. 103 | public static int RemoveWhere([NotNull] this ICollection collection, [NotNull] [InstantHandle] Func predicate) { 104 | if (collection == null) throw new ArgumentNullException(nameof(collection)); 105 | if (predicate == null) throw new ArgumentNullException(nameof(predicate)); 106 | Contract.EndContractBlock(); 107 | 108 | var list = collection as IList; 109 | if (list != null) 110 | return RemoveFromListWhere(list, predicate); 111 | 112 | var index = 0; 113 | var itemsToRemove = new List(); 114 | foreach (var item in collection) { 115 | if (predicate(item, index)) 116 | itemsToRemove.Add(item); 117 | 118 | index += 1; 119 | } 120 | collection.RemoveAll(itemsToRemove); 121 | return itemsToRemove.Count; 122 | } 123 | 124 | private static int RemoveFromListWhere([NotNull] IList list, [NotNull] [InstantHandle] Func predicate) { 125 | var removedCount = 0; 126 | for (var i = list.Count - 1; i >= 0; i--) { 127 | if (!predicate(list[i], i)) 128 | continue; 129 | 130 | list.RemoveAt(i); 131 | removedCount += 1; 132 | } 133 | 134 | return removedCount; 135 | } 136 | } 137 | } -------------------------------------------------------------------------------- /[Main]/Internal/ReSharperAnnotations.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2007-2012 JetBrains s.r.o. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | using System; 18 | 19 | // ReSharper disable CheckNamespace 20 | namespace JetBrains.Annotations { 21 | // ReSharper restore CheckNamespace 22 | [AttributeUsage(AttributeTargets.Constructor | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] 23 | internal sealed class StringFormatMethodAttribute : Attribute { 24 | public StringFormatMethodAttribute(string formatParameterName) { 25 | FormatParameterName = formatParameterName; 26 | } 27 | 28 | [UsedImplicitly] 29 | public string FormatParameterName { get; private set; } 30 | } 31 | 32 | [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] 33 | internal sealed class InvokerParameterNameAttribute : Attribute { } 34 | 35 | [AttributeUsage(AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Delegate | AttributeTargets.Field, AllowMultiple = false, Inherited = true)] 36 | internal sealed class CanBeNullAttribute : Attribute { } 37 | 38 | [AttributeUsage(AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Delegate | AttributeTargets.Field, AllowMultiple = false, Inherited = true)] 39 | internal sealed class NotNullAttribute : Attribute { } 40 | 41 | [AttributeUsage(AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Delegate | AttributeTargets.Field)] 42 | internal sealed class ItemNotNullAttribute : Attribute { } 43 | 44 | [AttributeUsage(AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Delegate | AttributeTargets.Field)] 45 | internal sealed class ItemCanBeNullAttribute : Attribute { } 46 | 47 | [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)] 48 | internal sealed class ContractAnnotationAttribute : Attribute { 49 | public ContractAnnotationAttribute([NotNull] string fdt) 50 | : this(fdt, false) { 51 | } 52 | 53 | public ContractAnnotationAttribute([NotNull] string fdt, bool forceFullStates) { 54 | FDT = fdt; 55 | ForceFullStates = forceFullStates; 56 | } 57 | 58 | public string FDT { get; private set; } 59 | public bool ForceFullStates { get; private set; } 60 | } 61 | 62 | [AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = true)] 63 | internal sealed class UsedImplicitlyAttribute : Attribute { 64 | [UsedImplicitly] 65 | public UsedImplicitlyAttribute() 66 | : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) { } 67 | 68 | [UsedImplicitly] 69 | public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) { 70 | UseKindFlags = useKindFlags; 71 | TargetFlags = targetFlags; 72 | } 73 | 74 | [UsedImplicitly] 75 | public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags) 76 | : this(useKindFlags, ImplicitUseTargetFlags.Default) { } 77 | 78 | [UsedImplicitly] 79 | public UsedImplicitlyAttribute(ImplicitUseTargetFlags targetFlags) 80 | : this(ImplicitUseKindFlags.Default, targetFlags) { } 81 | 82 | [UsedImplicitly] 83 | public ImplicitUseKindFlags UseKindFlags { get; private set; } 84 | 85 | [UsedImplicitly] 86 | public ImplicitUseTargetFlags TargetFlags { get; private set; } 87 | } 88 | 89 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] 90 | internal sealed class MeansImplicitUseAttribute : Attribute { 91 | [UsedImplicitly] 92 | public MeansImplicitUseAttribute() 93 | : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) { } 94 | 95 | [UsedImplicitly] 96 | public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) { 97 | UseKindFlags = useKindFlags; 98 | TargetFlags = targetFlags; 99 | } 100 | 101 | [UsedImplicitly] 102 | public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags) 103 | : this(useKindFlags, ImplicitUseTargetFlags.Default) { 104 | } 105 | 106 | [UsedImplicitly] 107 | public MeansImplicitUseAttribute(ImplicitUseTargetFlags targetFlags) 108 | : this(ImplicitUseKindFlags.Default, targetFlags) { } 109 | 110 | [UsedImplicitly] 111 | public ImplicitUseKindFlags UseKindFlags { get; private set; } 112 | 113 | /// 114 | /// Gets value indicating what is meant to be used 115 | /// 116 | [UsedImplicitly] 117 | public ImplicitUseTargetFlags TargetFlags { get; private set; } 118 | } 119 | 120 | [Flags] 121 | internal enum ImplicitUseKindFlags { 122 | Default = Access | Assign | InstantiatedWithFixedConstructorSignature, 123 | Access = 1, 124 | Assign = 2, 125 | InstantiatedWithFixedConstructorSignature = 4, 126 | InstantiatedNoFixedConstructorSignature = 8, 127 | } 128 | 129 | [Flags] 130 | internal enum ImplicitUseTargetFlags { 131 | Default = Itself, 132 | Itself = 1, 133 | Members = 2, 134 | WithMembers = Itself | Members 135 | } 136 | 137 | [MeansImplicitUse] 138 | internal sealed class PublicAPIAttribute : Attribute { 139 | public PublicAPIAttribute() { } 140 | public PublicAPIAttribute(string comment) { } 141 | } 142 | 143 | [AttributeUsage(AttributeTargets.Parameter, Inherited = true)] 144 | internal sealed class InstantHandleAttribute : Attribute { } 145 | 146 | [AttributeUsage(AttributeTargets.Method, Inherited = true)] 147 | internal sealed class PureAttribute : Attribute { } 148 | } -------------------------------------------------------------------------------- /[Main]/[Main].csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | AshMind.Extensions 4 | AshMind.Extensions 5 | Extension methods that could have been written by BCL authors. 6 | 3.0.0-pre-20190808-01 7 | Andrey Shchekin 8 | net40;net45;net472;netstandard1.1;netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp2.0;netcoreapp2.1;netcoreapp2.2;netcoreapp3.0 9 | AshMind.Extensions 10 | extensions 11 | 12 | 3.0.0 (pre-release) Removed Ruby-like APIs and improved naming in few other cases. Added nullable reference types. Obsoleted several "static-wrapper" APIs. 13 | 2.0.0 (pre-release) Added support for .NET Standard. Removed multiple obsolete APIs. Improved consistency of AsReadOnly*() overloads. 14 | 1.8.1 (!Please upgrade to this if you use SubstringAfter!) Fixed bug in SubstringAfter. 15 | 1.8.0 Added [Obsolete] to several APIs that will be changed/removed. Added special case EnumerateRange for List<T>. Added StringBuilder.AppendJoin(). 16 | 1.7.2 Added [ContractAnnotation("null=>null")] to String.IsNullOrEmpty(). 17 | 1.7.1 Removed unnecessary ForEach() call from Collection.AddRange()/RemoveRange() to improve performance. 18 | 1.7.0 Added StringComparison overloads to String.RemoveStart() and String.RemoveEnd(). 19 | 20 | https://github.com/ashmind/ashmind-extensions 21 | https://github.com/ashmind/ashmind-extensions.git 22 | false 23 | false 24 | 8.0 25 | enable 26 | true 27 | 28 | 29 | 30 | $(DefineConstants); No_MethodInfo_CreateDelegate; No_Char_ToUpperOrLower_Culture; No_ReadOnlyCollections; No_Property_SetValue_NoIndex; No_StringBuilder_AppendJoin; No_Enumerable_ToHashSet; No_ReadOnlyDictionary_GetValueOrDefault; No_Nullability_Attributes 31 | 32 | 33 | 34 | $(DefineConstants); No_StringBuilder_AppendJoin; No_Enumerable_ToHashSet; No_ReadOnlyDictionary_GetValueOrDefault; No_Nullability_Attributes 35 | 36 | 37 | 38 | $(DefineConstants); No_StringBuilder_AppendJoin; No_ReadOnlyDictionary_GetValueOrDefault; No_Nullability_Attributes 39 | 40 | 41 | 42 | $(DefineConstants); TypeInfo; No_Contracts; No_FileInfo; No_Char_ToUpperOrLower_Culture; No_ICustomAttributeProvider; No_StringBuilder_AppendJoin; No_Enumerable_ToHashSet; No_ReadOnlyDictionary_GetValueOrDefault; No_Nullability_Attributes 43 | 44 | 45 | 46 | $(DefineConstants); TypeInfo; No_Contracts; No_FileInfo; No_Char_ToUpperOrLower_Culture; No_ICustomAttributeProvider; No_StringBuilder_AppendJoin; No_Enumerable_ToHashSet; No_ReadOnlyDictionary_GetValueOrDefault; No_Nullability_Attributes 47 | 48 | 49 | 50 | $(DefineConstants); No_StringBuilder_AppendJoin; No_Enumerable_ToHashSet; No_ReadOnlyDictionary_GetValueOrDefault; No_Nullability_Attributes 51 | 52 | 53 | 54 | $(DefineConstants); No_Nullability_Attributes 55 | 56 | 57 | 58 | $(DefineConstants); No_Nullability_Attributes 59 | 60 | 61 | 62 | $(DefineConstants); No_Nullability_Attributes 63 | 64 | 65 | 66 | $(DefineConstants); No_Nullability_Attributes 67 | 68 | 69 | 70 | $(DefineConstants); 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /[Main]/DateTimeExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace AshMind.Extensions { 4 | /// 5 | /// Provides a set of extension methods for operations on and . 6 | /// 7 | public static class DateTimeExtensions { 8 | /// 9 | /// Returns a new object that removes time information beyond millisecond precision from the provided instance. 10 | /// 11 | /// The object that would be used as a source for the non-truncated time parts. 12 | /// 13 | /// An object that is equivalent to up to millisecond precision, and empty beyond milliseconds. 14 | /// 15 | /// 16 | /// Note that the end result might be in the future relative to the original . represents 17 | /// a rounded value for ticks — so 10 milliseconds might internally be 9.6 milliseconds. However this information is lost after this method, and 18 | /// the value would be replaced with 10 milliseconds. 19 | /// 20 | public static DateTimeOffset TruncateToMilliseconds(this DateTimeOffset date) { 21 | return new DateTimeOffset(date.Year, date.Month, date.Day, date.Hour, date.Minute, date.Second, date.Millisecond, date.Offset); 22 | } 23 | 24 | /// 25 | /// Returns a new object that removes time information beyond second precision from the provided instance. 26 | /// 27 | /// The object that would be used as a source for the non-truncated time parts. 28 | /// 29 | /// An object that is equivalent to up to second precision, and empty beyond seconds. 30 | /// 31 | public static DateTimeOffset TruncateToSeconds(this DateTimeOffset date) { 32 | return new DateTimeOffset(date.Year, date.Month, date.Day, date.Hour, date.Minute, date.Second, 0, date.Offset); 33 | } 34 | 35 | /// 36 | /// Returns a new object that removes time information beyond minute precision from the provided instance. 37 | /// 38 | /// The object that would be used as a source for the non-truncated time parts. 39 | /// 40 | /// An object that is equivalent to up to minute precision, and empty beyond minutes. 41 | /// 42 | public static DateTimeOffset TruncateToMinutes(this DateTimeOffset date) { 43 | return new DateTimeOffset(date.Year, date.Month, date.Day, date.Hour, date.Minute, 0, 0, date.Offset); 44 | } 45 | 46 | /// 47 | /// Returns a new object that removes time information beyond hour precision from the provided instance. 48 | /// 49 | /// The object that would be used as a source for the non-truncated time parts. 50 | /// 51 | /// An object that is equivalent to up to hour precision, and empty beyond hours. 52 | /// 53 | public static DateTimeOffset TruncateToHours(this DateTimeOffset date) { 54 | return new DateTimeOffset(date.Year, date.Month, date.Day, date.Hour, 0, 0, 0, date.Offset); 55 | } 56 | 57 | /// 58 | /// Returns a new object that removes time information beyond millisecond precision from the provided instance. 59 | /// 60 | /// The object that would be used as a source for the non-truncated time parts. 61 | /// 62 | /// An object that is equivalent to up to millisecond precision, and empty beyond milliseconds. 63 | /// 64 | /// 65 | /// Note that the end result might be in the future relative to the original . represents 66 | /// a rounded value for ticks — so 10 milliseconds might internally be 9.6 milliseconds. However this information is lost after this method, and 67 | /// the value would be replaced with 10 milliseconds. 68 | /// 69 | public static DateTime TruncateToMilliseconds(this DateTime date) { 70 | return new DateTime(date.Year, date.Month, date.Day, date.Hour, date.Minute, date.Second, date.Millisecond); 71 | } 72 | 73 | /// 74 | /// Returns a new object that removes time information beyond second precision from the provided instance. 75 | /// 76 | /// The object that would be used as a source for the non-truncated time parts. 77 | /// 78 | /// An object that is equivalent to up to second precision, and empty beyond seconds. 79 | /// 80 | public static DateTime TruncateToSeconds(this DateTime date) { 81 | return new DateTime(date.Year, date.Month, date.Day, date.Hour, date.Minute, date.Second, 0); 82 | } 83 | 84 | /// 85 | /// Returns a new object that removes time information beyond minute precision from the provided instance. 86 | /// 87 | /// The object that would be used as a source for the non-truncated time parts. 88 | /// 89 | /// An object that is equivalent to up to minute precision, and empty beyond minutes. 90 | /// 91 | public static DateTime TruncateToMinutes(this DateTime date) { 92 | return new DateTime(date.Year, date.Month, date.Day, date.Hour, date.Minute, 0, 0); 93 | } 94 | 95 | /// 96 | /// Returns a new object that removes time information beyond hour precision from the provided instance. 97 | /// 98 | /// The object that would be used as a source for the non-truncated time parts. 99 | /// 100 | /// An object that is equivalent to up to hour precision, and empty beyond hours. 101 | /// 102 | public static DateTime TruncateToHours(this DateTime date) { 103 | return new DateTime(date.Year, date.Month, date.Day, date.Hour, 0, 0, 0); 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /[Main]/DictionaryExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using JetBrains.Annotations; 5 | using Contracts = System.Diagnostics.Contracts; 6 | using CodeAnalysis = System.Diagnostics.CodeAnalysis; 7 | 8 | namespace AshMind.Extensions { 9 | /// 10 | /// Provides a set of extension methods for operations on . 11 | /// 12 | public static class DictionaryExtensions { 13 | /// Gets the value associated with the specified key, or a default value if the key was not found. 14 | /// The dictionary to get value from. 15 | /// The key of the value to get. 16 | /// The type of keys in the . 17 | /// The type of values in the . 18 | /// The value associated with the specified key, if the key is found; otherwise, the default value for the type. 19 | [Contracts.Pure] [Pure] [CanBeNull] 20 | [return: CodeAnalysis.MaybeNull] 21 | public static TValue GetValueOrDefault([NotNull] this IDictionary dictionary, [NotNull] TKey key) 22 | where TKey: notnull 23 | { 24 | TValue value; 25 | var succeeded = dictionary.TryGetValue(key, out value); 26 | return succeeded ? value : default!; // default! is a limitation of NRT, see https://github.com/dotnet/roslyn/issues/30953 27 | } 28 | 29 | /// Gets the value associated with the specified key, or adds and returns the new value if the key was not found. 30 | /// The dictionary to get value from. 31 | /// The key of the value to get or add. 32 | /// The value to add if is not found. 33 | /// The type of keys in the . 34 | /// The type of values in the . 35 | /// The value associated with the specified key, if the key is found; otherwise, the . 36 | [CanBeNull] 37 | public static TValue GetOrAdd([NotNull] this IDictionary dictionary, [NotNull] TKey key, [CanBeNull] TValue value) 38 | where TKey : notnull 39 | { 40 | TValue found; 41 | if (dictionary.TryGetValue(key, out found)) 42 | return found; 43 | 44 | dictionary.Add(key, value); 45 | return value; 46 | } 47 | 48 | /// Gets the value associated with the specified key, or adds and returns a new value by using the specified function if the key was not found. 49 | /// The dictionary to get value from. 50 | /// The key of the value to get or add. 51 | /// The function used to generate a value for the if it was not found. 52 | /// The type of keys in the . 53 | /// The type of values in the . 54 | /// The value associated with the specified key, if the key is found; otherwise, the value returned by . 55 | [CanBeNull] 56 | public static TValue GetOrAdd([NotNull] this IDictionary dictionary, [NotNull] TKey key, [NotNull] [InstantHandle] Func valueFactory) 57 | where TKey : notnull 58 | { 59 | TValue found; 60 | if (dictionary.TryGetValue(key, out found)) 61 | return found; 62 | 63 | var value = valueFactory(key); 64 | dictionary.Add(key, value); 65 | return value; 66 | } 67 | 68 | #if !No_ReadOnlyCollections 69 | /// 70 | /// Converts to . 71 | /// 72 | /// The type of keys in . 73 | /// The type of values in . 74 | /// The dictionary to convert. 75 | /// 76 | /// A that acts as a read-only wrapper around the current . 77 | /// 78 | /// If the is already of type this method returns it directly. Otherwise, it 79 | /// returns an new instance of acting as a read-only wrapper around the . 80 | /// 81 | [Contracts.Pure] [Pure] 82 | public static IReadOnlyDictionary AsReadOnlyDictionary([NotNull] this IDictionary dictionary) 83 | where TKey : notnull 84 | { 85 | return (dictionary as IReadOnlyDictionary) ?? new ReadOnlyDictionary(dictionary); 86 | } 87 | 88 | #if No_ReadOnlyDictionary_GetValueOrDefault 89 | /// Gets the value associated with the specified key, or a default value if the key was not found. 90 | /// The dictionary to get value from. 91 | /// The key of the value to get. 92 | /// The type of keys in the . 93 | /// The type of values in the . 94 | /// The value associated with the specified key, if the key is found; otherwise, the default value for the type. 95 | [Contracts.Pure] [Pure] 96 | [return: CodeAnalysis.MaybeNull] 97 | public static TValue GetValueOrDefault([NotNull] this IReadOnlyDictionary dictionary, [NotNull] TKey key) 98 | where TKey: notnull 99 | { 100 | TValue value; 101 | dictionary.TryGetValue(key, out value); 102 | return value; // IReadOnlyDictionary<,> interface promises value == default(TValue) if not found 103 | } 104 | #endif 105 | 106 | /// Gets the value associated with the specified key, or a default value if the key was not found. 107 | /// The dictionary to get value from. 108 | /// The key of the value to get. 109 | /// The type of keys in the . 110 | /// The type of values in the . 111 | /// The value associated with the specified key, if the key is found; otherwise, the default value for the type. 112 | [Contracts.Pure] [Pure] 113 | [return: CodeAnalysis.MaybeNull] 114 | public static TValue GetValueOrDefault([NotNull] this Dictionary dictionary, [NotNull] TKey key) 115 | where TKey : notnull 116 | { 117 | return ((IReadOnlyDictionary)dictionary).GetValueOrDefault(key); 118 | } 119 | #endif 120 | } 121 | } -------------------------------------------------------------------------------- /[Main]/TypeExtensions.cs: -------------------------------------------------------------------------------- 1 | #if !TypeInfo 2 | using System; 3 | using System.Diagnostics.Contracts; 4 | using System.Linq; 5 | using JetBrains.Annotations; 6 | using Contracts = System.Diagnostics.Contracts; 7 | using PureAttribute = JetBrains.Annotations.PureAttribute; 8 | 9 | namespace AshMind.Extensions { 10 | /// 11 | /// Provides a set of extension methods for operations on . 12 | /// 13 | public static class TypeExtensions { 14 | /// 15 | /// Determines whether an instance of the current type can be assigned from an instance of the specified type. 16 | /// 17 | /// The type to compare with the current type. 18 | /// The current type. 19 | /// 20 | /// true if and the represent the same type, or if 21 | /// is in the inheritance hierarchy of , or if the 22 | /// is an interface that implements, or if 23 | /// is a generic type parameter and represents one of the 24 | /// constraints of , or if represents a value type and 25 | /// represents Nullable<T>. false if none of these conditions are true. 26 | /// 27 | [Contracts.Pure] [Pure] 28 | public static bool IsAssignableFrom([NotNull] this Type type) 29 | { 30 | if (type == null) throw new ArgumentNullException(nameof(type)); 31 | Contract.EndContractBlock(); 32 | 33 | return type.IsAssignableFrom(typeof(T)); 34 | } 35 | 36 | /// 37 | /// Determines whether an instance of the current type can be assigned to an instance of the specified type. 38 | /// 39 | /// The type to compare with the current type. 40 | /// The current type. 41 | /// 42 | /// true if and the represent the same type, or if 43 | /// is in the inheritance hierarchy of , or if the 44 | /// is an interface that implements, or if is a generic type 45 | /// parameter and represents one of the constraints of , or 46 | /// if represents a value type and represents 47 | /// Nullable<type>. false if none of these conditions are true. 48 | /// 49 | [Contracts.Pure] [Pure] 50 | public static bool IsAssignableTo([NotNull] this Type type) 51 | { 52 | if (type == null) throw new ArgumentNullException(nameof(type)); 53 | Contract.EndContractBlock(); 54 | 55 | return type.IsAssignableTo(typeof(T)); 56 | } 57 | 58 | /// 59 | /// Determines whether an instance of the current type can be assigned to an instance of the specified type. 60 | /// 61 | /// The current type. 62 | /// The type to compare with the current type. 63 | /// 64 | /// true if and the represent the same type, or if 65 | /// is in the inheritance hierarchy of , or if the 66 | /// is an interface that implements, or if 67 | /// is a generic type parameter and represents one of 68 | /// the constraints of , or if represents a value type and 69 | /// represents Nullable<type>. false if none of these conditions 70 | /// are true, or if is null. 71 | /// 72 | [ContractAnnotation("other:null=>false")] 73 | [Contracts.Pure] [Pure] 74 | public static bool IsAssignableTo([NotNull] this Type type, [CanBeNull] Type? other) 75 | { 76 | if (type == null) throw new ArgumentNullException(nameof(type)); 77 | Contract.EndContractBlock(); 78 | 79 | if (other == null) 80 | return false; 81 | 82 | return other.IsAssignableFrom(type); 83 | } 84 | 85 | [Contracts.Pure] [Pure] 86 | public static bool IsSameAsOrSubclassOf([NotNull] this Type type, [NotNull] Type otherType) 87 | { 88 | if (type == null) throw new ArgumentNullException(nameof(type)); 89 | if (otherType == null) throw new ArgumentNullException(nameof(otherType)); 90 | Contract.EndContractBlock(); 91 | 92 | return type == otherType || type.IsSubclassOf(otherType); 93 | } 94 | 95 | [Contracts.Pure] [Pure] 96 | public static bool IsSameAsOrSubclassOf([NotNull] this Type type) 97 | { 98 | if (type == null) throw new ArgumentNullException(nameof(type)); 99 | Contract.EndContractBlock(); 100 | 101 | return type.IsSameAsOrSubclassOf(typeof(TClass)); 102 | } 103 | 104 | [Contracts.Pure] [Pure] 105 | public static bool IsGenericTypeDefinedAs([NotNull] this Type type, [NotNull] Type otherType) { 106 | if (type == null) throw new ArgumentNullException(nameof(type)); 107 | Contract.EndContractBlock(); 108 | 109 | if (!type.IsGenericType) 110 | return false; 111 | 112 | return type.GetGenericTypeDefinition() == otherType; 113 | } 114 | 115 | /// 116 | /// Determines whether the specified interface is implemented by the specified type. 117 | /// 118 | /// The interface that might be implemented by the . 119 | /// The type for which the fact of implementation will be dermined. 120 | /// true if implements ; otherwise, false. 121 | [Contracts.Pure] [Pure] 122 | public static bool HasInterface([NotNull] this Type type) 123 | where TInterface : class 124 | { 125 | if (type == null) throw new ArgumentNullException(nameof(type)); 126 | Contract.EndContractBlock(); 127 | 128 | return type.HasInterface(typeof(TInterface)); 129 | } 130 | 131 | /// 132 | /// Determines whether a given interface is implemented by a specified type. 133 | /// 134 | /// The type for which the fact of implementation will be dermined. 135 | /// The interface that might be implemented by the . 136 | /// true if implements ; otherwise, false. 137 | [Contracts.Pure] [Pure] 138 | public static bool HasInterface([NotNull] this Type type, [NotNull] Type interfaceType) { 139 | if (type == null) throw new ArgumentNullException(nameof(type)); 140 | if (interfaceType == null) throw new ArgumentNullException(nameof(interfaceType)); 141 | Contract.EndContractBlock(); 142 | 143 | return type.GetInterfaces().Contains(interfaceType); 144 | } 145 | } 146 | } 147 | #endif -------------------------------------------------------------------------------- /[Main]/TypeInfoExtensions.cs: -------------------------------------------------------------------------------- 1 | #if TypeInfo 2 | using System; 3 | using System.Diagnostics.Contracts; 4 | using System.Linq; 5 | using System.Reflection; 6 | using JetBrains.Annotations; 7 | using Contracts = System.Diagnostics.Contracts; 8 | using PureAttribute = JetBrains.Annotations.PureAttribute; 9 | 10 | namespace AshMind.Extensions { 11 | /// 12 | /// Provides a set of extension methods for operations on . 13 | /// 14 | public static class TypeInfoExtensions { 15 | /// 16 | /// Determines whether an instance of the current type can be assigned from an instance of the specified type. 17 | /// 18 | /// The type to compare with the current type. 19 | /// The current type. 20 | /// 21 | /// true if and the represent the same type, or if 22 | /// is in the inheritance hierarchy of , or if the 23 | /// is an interface that implements, or if 24 | /// is a generic type parameter and represents one of the 25 | /// constraints of , or if represents a value type and 26 | /// represents Nullable<T>. false if none of these conditions are true. 27 | /// 28 | [Contracts.Pure] [Pure] 29 | public static bool IsAssignableFrom([NotNull] this TypeInfo type) 30 | { 31 | if (type == null) throw new ArgumentNullException(nameof(type)); 32 | Contract.EndContractBlock(); 33 | 34 | return type.IsAssignableFrom(typeof(T).GetTypeInfo()); 35 | } 36 | 37 | /// 38 | /// Determines whether an instance of the current type can be assigned to an instance of the specified type. 39 | /// 40 | /// The type to compare with the current type. 41 | /// The current type. 42 | /// 43 | /// true if and the represent the same type, or if 44 | /// is in the inheritance hierarchy of , or if the 45 | /// is an interface that implements, or if is a generic type 46 | /// parameter and represents one of the constraints of , or 47 | /// if represents a value type and represents 48 | /// Nullable<type>. false if none of these conditions are true. 49 | /// 50 | [Contracts.Pure] [Pure] 51 | public static bool IsAssignableTo([NotNull] this TypeInfo type) 52 | { 53 | if (type == null) throw new ArgumentNullException(nameof(type)); 54 | Contract.EndContractBlock(); 55 | 56 | return type.IsAssignableTo(typeof(T).GetTypeInfo()); 57 | } 58 | 59 | /// 60 | /// Determines whether an instance of the current type can be assigned to an instance of the specified type. 61 | /// 62 | /// The current type. 63 | /// The type to compare with the current type. 64 | /// 65 | /// true if and the represent the same type, or if 66 | /// is in the inheritance hierarchy of , or if the 67 | /// is an interface that implements, or if 68 | /// is a generic type parameter and represents one of 69 | /// the constraints of , or if represents a value type and 70 | /// represents Nullable<type>. false if none of these conditions 71 | /// are true, or if is null. 72 | /// 73 | [ContractAnnotation("other:null=>false")] 74 | [Contracts.Pure] 75 | [Pure] 76 | public static bool IsAssignableTo([NotNull] this TypeInfo type, [CanBeNull] TypeInfo? other) 77 | { 78 | if (type == null) throw new ArgumentNullException(nameof(type)); 79 | Contract.EndContractBlock(); 80 | 81 | if (other == null) 82 | return false; 83 | 84 | return other.IsAssignableFrom(type); 85 | } 86 | 87 | [Contracts.Pure] 88 | [Pure] 89 | public static bool IsSameAsOrSubclassOf([NotNull] this TypeInfo type, [NotNull] Type otherType) 90 | { 91 | if (type == null) throw new ArgumentNullException(nameof(type)); 92 | if (otherType == null) throw new ArgumentNullException(nameof(otherType)); 93 | Contract.EndContractBlock(); 94 | 95 | return type.AsType() == otherType || type.IsSubclassOf(otherType); 96 | } 97 | 98 | [Contracts.Pure] [Pure] 99 | public static bool IsSubclassOf([NotNull] this TypeInfo type) 100 | where T : class 101 | { 102 | if (type == null) throw new ArgumentNullException(nameof(type)); 103 | Contract.EndContractBlock(); 104 | 105 | return type.IsSubclassOf(typeof(T)); 106 | } 107 | 108 | [Contracts.Pure] [Pure] 109 | public static bool IsGenericTypeDefinedAs([NotNull] this TypeInfo type, [NotNull] Type otherType) { 110 | if (type == null) throw new ArgumentNullException(nameof(type)); 111 | Contract.EndContractBlock(); 112 | 113 | if (!type.IsGenericType) 114 | return false; 115 | 116 | return type.GetGenericTypeDefinition() == otherType; 117 | } 118 | 119 | /// 120 | /// Determines whether the specified interface is implemented by the specified type. 121 | /// 122 | /// The interface that might be implemented by the . 123 | /// The type for which the fact of implementation will be dermined. 124 | /// true if implements ; otherwise, false. 125 | [Contracts.Pure] [Pure] 126 | public static bool HasInterface([NotNull] this TypeInfo type) 127 | where TInterface : class 128 | { 129 | if (type == null) throw new ArgumentNullException(nameof(type)); 130 | Contract.EndContractBlock(); 131 | 132 | return type.HasInterface(typeof(TInterface)); 133 | } 134 | 135 | /// 136 | /// Determines whether a given interface is implemented by a specified type. 137 | /// 138 | /// The type for which the fact of implementation will be dermined. 139 | /// The interface that might be implemented by the . 140 | /// true if implements ; otherwise, false. 141 | [Contracts.Pure] [Pure] 142 | public static bool HasInterface([NotNull] this TypeInfo type, [NotNull] Type interfaceType) { 143 | if (type == null) throw new ArgumentNullException(nameof(type)); 144 | if (interfaceType == null) throw new ArgumentNullException(nameof(interfaceType)); 145 | Contract.EndContractBlock(); 146 | 147 | return type.ImplementedInterfaces.Contains(interfaceType); 148 | } 149 | } 150 | } 151 | #endif -------------------------------------------------------------------------------- /Tests/EnumerableExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.Linq; 5 | using Xunit; 6 | 7 | // ReSharper disable PossibleNullReferenceException 8 | 9 | namespace AshMind.Extensions.Tests 10 | { 11 | public class EnumerableExtensionsTests { 12 | [Fact] 13 | public void EmptyIfNull_WhenEnumerableIsNotNull_ReturnsSameEnumerable() { 14 | var enumerable = Enumerable.Empty(); 15 | Assert.Same(enumerable, enumerable.EmptyIfNull()); 16 | } 17 | 18 | [Fact] 19 | public void EmptyIfNull_WhenEnumerableIsNull_ReturnsEmptyEnmumerable() { 20 | // ReSharper disable once ExpressionIsAlwaysNull 21 | var result = ((IEnumerable?)null).EmptyIfNull(); 22 | Assert.NotNull(result); 23 | Assert.Equal(Enumerable.Empty(), result); 24 | } 25 | 26 | [Fact] 27 | public void Any_ReceivesCorrectIndex() { 28 | var list = new List { 0, 1, 2, 3 }; 29 | // ReSharper disable once ReturnValueOfPureMethodIsNotUsed 30 | list.Any((item, index) => { 31 | Assert.Equal(item, index); 32 | return false; 33 | }); 34 | } 35 | 36 | [Theory] 37 | [InlineData(2, true)] 38 | [InlineData(5, false)] 39 | public void Any_ProducesExpectedResult(int input, bool expected) { 40 | var list = new List { 0, 1, 2, 3 }; 41 | var result = list.Any((item, index) => item == input); 42 | 43 | Assert.Equal(expected, result); 44 | } 45 | 46 | [Fact] 47 | public void HavingMax_ReturnsAllMaxValues() { 48 | var items = new[] { 49 | new { Name = "Andrey", Salary = 15000 }, 50 | new { Name = "Stan", Salary = 9000 }, 51 | new { Name = "William", Salary = 15000 }, 52 | }; 53 | 54 | Assert.Equal( 55 | items.HavingMax(p => p.Salary), 56 | new[] { items[0], items[2] } 57 | ); 58 | } 59 | 60 | [Fact] 61 | public void HavingMax_ForSingleValue_ReturnsIt() { 62 | var items = new[] { new { Name = "Andrey", Salary = 15000 } }; 63 | 64 | Assert.Equal( 65 | items.HavingMax(p => p.Salary), 66 | new[] { items[0] } 67 | ); 68 | } 69 | 70 | [Fact] 71 | public void HavingMax_ForSingleValueThatIsDefaultForType_ReturnsIt() { 72 | var items = new[] { new { Name = "Andrey", Level = 0 } }; 73 | 74 | Assert.Equal(items.HavingMax(p => p.Level), items); 75 | } 76 | 77 | [Fact] 78 | public void HavingMin_ReturnsAllMinValues() { 79 | var items = new[] { 80 | new { Name = "Andrey", Salary = 9000 }, 81 | new { Name = "Stan", Salary = 9000 }, 82 | new { Name = "William", Salary = 15000 }, 83 | }; 84 | 85 | Assert.Equal( 86 | items.HavingMin(p => p.Salary), 87 | new[] { items[0], items[1] } 88 | ); 89 | } 90 | 91 | [Theory] 92 | [InlineData("A", "A:0")] 93 | [InlineData("A,A,A", "A:0,1,2")] 94 | [InlineData("A,A,A,B,C,C,A,A,B", "A:0,1,2; B:3; C:4,5; A:6,7; B:8")] 95 | public void GroupAdjacentBy_GroupsByKeyCorrectly(string itemsString, string expected) { 96 | var items = itemsString.Split(',').Select((key, index) => new { key, index }); 97 | var grouped = items.GroupAdjacentBy(x => x.key, x => x.index); 98 | var groupedString = string.Join("; ", grouped.Select(g => g.Key + ":" + string.Join(",", g))); 99 | 100 | Assert.Equal(expected, groupedString); 101 | } 102 | 103 | [Fact] 104 | public void GroupAdjacentBy_ReturnsEmptySequence_WhenPassedEmpty() { 105 | var grouped = (new string[0]).GroupAdjacentBy(x => x); 106 | Assert.Empty(grouped); 107 | } 108 | 109 | [Fact] 110 | public void GroupAdjacentBy_DoesNotCallKeySelectorImmediately() { 111 | var items = new[] { new { key = 1 } }; 112 | var selectorCalled = false; 113 | 114 | // ReSharper disable once ReturnValueOfPureMethodIsNotUsed 115 | items.GroupAdjacentBy(x => { 116 | selectorCalled = true; 117 | return x.key; 118 | }); 119 | 120 | Assert.False(selectorCalled); 121 | } 122 | 123 | [Fact] 124 | public void GroupAdjacentBy_DoesNotCallElementSelectorImmediately() { 125 | var items = new[] { new { key = 1 } }; 126 | var selectorCalled = false; 127 | 128 | // ReSharper disable once ReturnValueOfPureMethodIsNotUsed 129 | items.GroupAdjacentBy(x => x.key, x => { 130 | selectorCalled = true; 131 | return x; 132 | }); 133 | 134 | Assert.False(selectorCalled); 135 | } 136 | 137 | [Fact] 138 | public void GroupAdjacentBy_DoesNotCallResultSelectorImmediately() { 139 | var items = new[] { new { key = 1 } }; 140 | var selectorCalled = false; 141 | 142 | // ReSharper disable once ReturnValueOfPureMethodIsNotUsed 143 | items.GroupAdjacentBy(x => x.key, x => x, (_, xs) => { 144 | selectorCalled = true; 145 | return xs; 146 | }); 147 | 148 | Assert.False(selectorCalled); 149 | } 150 | 151 | [Fact] 152 | public void AsList_UsesSameInstance_ForList() { 153 | var list = new List(); 154 | Assert.Same(list, list.AsList()); 155 | } 156 | 157 | [Fact] 158 | public void AsList_CreatesNewList_ForEnumerable() { 159 | var enumerable = Enumerable.Range(1, 5); 160 | // ReSharper disable PossibleMultipleEnumeration 161 | Assert.Equal(enumerable, enumerable.AsList()); 162 | // ReSharper restore PossibleMultipleEnumeration 163 | } 164 | 165 | [Fact] 166 | public void AsReadOnlyList_UsesSameInstance_ForReadOnlyCollection() { 167 | var list = new List(); 168 | Assert.Same(list, list.AsReadOnlyList()); 169 | } 170 | 171 | [Fact] 172 | public void AsReadOnlyList_CreatesNewList_ForEnumerable() { 173 | var enumerable = Enumerable.Range(1, 5); 174 | // ReSharper disable PossibleMultipleEnumeration 175 | Assert.Equal(enumerable, enumerable.AsReadOnlyList()); 176 | // ReSharper restore PossibleMultipleEnumeration 177 | } 178 | 179 | [Fact] 180 | public void AsCollection_UsesSameInstance_ForCollection() { 181 | var collection = new Collection(); 182 | Assert.Same(collection, collection.AsCollection()); 183 | } 184 | 185 | [Fact] 186 | public void AsCollection_CreatesNewCollection_ForEnumerable() { 187 | var enumerable = Enumerable.Range(1, 5); 188 | // ReSharper disable PossibleMultipleEnumeration 189 | Assert.Equal(enumerable, enumerable.AsCollection()); 190 | // ReSharper restore PossibleMultipleEnumeration 191 | } 192 | 193 | [Fact] 194 | public void AsReadOnlyCollection_UsesSameInstance_ForReadOnlyCollection() { 195 | var collection = new Collection(); 196 | Assert.Same(collection, collection.AsReadOnlyCollection()); 197 | } 198 | 199 | [Fact] 200 | public void AsReadOnlyCollection_CreatesNewCollection_ForEnumerable() { 201 | var enumerable = Enumerable.Range(1, 5); 202 | // ReSharper disable PossibleMultipleEnumeration 203 | Assert.Equal(enumerable, enumerable.AsReadOnlyCollection()); 204 | // ReSharper restore PossibleMultipleEnumeration 205 | } 206 | 207 | [Theory] 208 | [InlineData(false)] 209 | [InlineData(true)] 210 | public void ToHashSet_IncludesAllItems(bool withComparer) { 211 | var list = new[] { 0, 1, 2, 3 }; 212 | var set = withComparer ? list.ToHashSet(EqualityComparer.Default) : list.ToHashSet(); 213 | 214 | Assert.Equal(list, set); 215 | } 216 | 217 | [Theory] 218 | [InlineData(false)] 219 | [InlineData(true)] 220 | public void ToHashSet_CollapsesDuplicateItems(bool withComparer) { 221 | var list = new[] { 0, 1, 2, 3, 2 }; 222 | var set = withComparer ? list.ToHashSet(EqualityComparer.Default) : list.ToHashSet(); 223 | 224 | Assert.Equal(list.Distinct(), set); 225 | } 226 | 227 | [Fact] 228 | public void ToHashSet_UsesCorrectComparer() { 229 | var list = new[] { "a", "b" }; 230 | var set = list.ToHashSet(StringComparer.InvariantCultureIgnoreCase); 231 | 232 | Assert.False(set.Add("A")); 233 | } 234 | } 235 | } -------------------------------------------------------------------------------- /[Main]/CustomAttributeProviderExtensions.cs: -------------------------------------------------------------------------------- 1 | #if !No_ICustomAttributeProvider 2 | using System; 3 | using System.Diagnostics.Contracts; 4 | using System.Linq; 5 | using System.Reflection; 6 | using JetBrains.Annotations; 7 | using Contracts = System.Diagnostics.Contracts; 8 | using PureAttribute = JetBrains.Annotations.PureAttribute; 9 | 10 | namespace AshMind.Extensions { 11 | /// 12 | /// Provides a set of extension methods for operations on . 13 | /// 14 | public static class CustomAttributeProviderExtensions { 15 | /// 16 | /// Gets the custom attributes of the specified type defined on this member. 17 | /// 18 | /// The type of attribute to search for. Only attributes that are assignable to this type are returned. 19 | /// The member which attributes will be retrieved. 20 | /// Specifies whether to search this member's inheritance chain to find the attributes. 21 | /// An array of custom attributes applied to this member, or an array with zero (0) elements if no attributes have been applied. 22 | /// A custom attribute type cannot be loaded. 23 | /// This member belongs to a type that is loaded into the reflection-only context. 24 | [Contracts.Pure] [Pure] [NotNull] 25 | public static TAttribute[] GetCustomAttributes([NotNull] this ICustomAttributeProvider member, bool inherit) 26 | where TAttribute : Attribute 27 | { 28 | if (member == null) throw new ArgumentNullException(nameof(member)); 29 | Contract.EndContractBlock(); 30 | 31 | return (TAttribute[])member.GetCustomAttributes(typeof(TAttribute), inherit); 32 | } 33 | 34 | /// 35 | /// Gets the custom attributes of the specified type defined on this member. 36 | /// 37 | /// The type of attribute to search for. Only attributes that are assignable to this type are returned. 38 | /// The member which attributes will be retrieved. 39 | /// An array of custom attributes applied to this member, or an array with zero (0) elements if no attributes have been applied. 40 | /// A custom attribute type cannot be loaded. 41 | /// This member belongs to a type that is loaded into the reflection-only context. 42 | [Contracts.Pure] [Pure] [NotNull] 43 | public static TAttribute[] GetCustomAttributes([NotNull] this ICustomAttributeProvider provider) 44 | where TAttribute : Attribute 45 | { 46 | if (provider == null) throw new ArgumentNullException(nameof(provider)); 47 | Contract.EndContractBlock(); 48 | 49 | return (TAttribute[])provider.GetCustomAttributes(typeof(TAttribute), true); 50 | } 51 | 52 | /// 53 | /// Gets the custom attributes of the specified type defined on this member. 54 | /// 55 | /// The provider which attributes will be retrieved. 56 | /// The type of attribute to search for. Only attributes that are assignable to this type are returned. 57 | /// An array of custom attributes applied to this member, or an array with zero (0) elements if no attributes have been applied. 58 | /// A custom attribute type cannot be loaded. 59 | /// If is a null reference (Nothing in Visual Basic). 60 | /// This provider is a type loaded into the reflection-only context or a member of such type. 61 | [Contracts.Pure] [Pure] [NotNull] 62 | public static object[] GetCustomAttributes([NotNull] this ICustomAttributeProvider provider, [NotNull] Type attributeType) { 63 | if (provider == null) throw new ArgumentNullException(nameof(provider)); 64 | Contract.EndContractBlock(); 65 | 66 | return provider.GetCustomAttributes(attributeType, true); 67 | } 68 | 69 | /// 70 | /// Returns an array containing all the custom attributes defined on this member. 71 | /// 72 | /// The member which attributes will be retrieved. 73 | /// An array that contains all the custom attributes, or an array with zero elements if no attributes are defined. 74 | /// A custom attribute type cannot be loaded. 75 | /// This member belongs to a type that is loaded into the reflection-only context. 76 | [Contracts.Pure] [Pure] [NotNull] 77 | public static object[] GetCustomAttributes([NotNull] this ICustomAttributeProvider provider) { 78 | if (provider == null) throw new ArgumentNullException(nameof(provider)); 79 | Contract.EndContractBlock(); 80 | 81 | return provider.GetCustomAttributes(true); 82 | } 83 | 84 | [Contracts.Pure] [Pure] [NotNull] 85 | public static TAttribute GetCustomAttribute([NotNull] this ICustomAttributeProvider provider, bool inherit) 86 | where TAttribute : Attribute 87 | { 88 | if (provider == null) throw new ArgumentNullException(nameof(provider)); 89 | Contract.EndContractBlock(); 90 | 91 | return provider.GetCustomAttributes(inherit).Single(); 92 | } 93 | 94 | [Contracts.Pure] [Pure] [NotNull] 95 | public static TAttribute GetCustomAttribute([NotNull] this ICustomAttributeProvider provider) 96 | where TAttribute : Attribute 97 | { 98 | if (provider == null) throw new ArgumentNullException(nameof(provider)); 99 | Contract.EndContractBlock(); 100 | 101 | return provider.GetCustomAttributes(false).Single(); 102 | } 103 | 104 | /// 105 | /// Indicates whether one or more instance of is defined on the specified member. 106 | /// 107 | /// The member to look up the attribute on. 108 | /// The type of the custom attributes. 109 | /// 110 | /// true if the is defined on this member; false otherwise. 111 | /// 112 | [Contracts.Pure] [Pure] 113 | public static bool IsDefined([NotNull] this ICustomAttributeProvider provider, [NotNull] Type attributeType) { 114 | if (provider == null) throw new ArgumentNullException(nameof(provider)); 115 | if (attributeType == null) throw new ArgumentNullException(nameof(attributeType)); 116 | Contract.EndContractBlock(); 117 | 118 | return provider.IsDefined(attributeType, false); 119 | } 120 | 121 | /// 122 | /// Indicates whether one or more instance of is defined on the specified member. 123 | /// 124 | /// The type of the custom attributes. 125 | /// The member to look up the attribute on. 126 | /// 127 | /// true if the is defined on this member; false otherwise. 128 | /// 129 | [Contracts.Pure] [Pure] 130 | public static bool IsDefined([NotNull] this ICustomAttributeProvider provider) 131 | where TAttribute : Attribute 132 | { 133 | if (provider == null) throw new ArgumentNullException(nameof(provider)); 134 | Contract.EndContractBlock(); 135 | 136 | return provider.IsDefined(typeof(TAttribute)); 137 | } 138 | 139 | /// 140 | /// Indicates whether one or more instance of is defined on the specified member. 141 | /// 142 | /// The type of the custom attributes. 143 | /// The member to look up the attribute on. 144 | /// When true, look up the hierarchy chain for the inherited custom attribute. 145 | /// 146 | /// true if the is defined on this member; false otherwise. 147 | /// 148 | [Contracts.Pure] [Pure] 149 | public static bool IsDefined([NotNull] this ICustomAttributeProvider provider, bool inherit) 150 | where TAttribute : Attribute 151 | { 152 | if (provider == null) throw new ArgumentNullException(nameof(provider)); 153 | Contract.EndContractBlock(); 154 | 155 | return provider.IsDefined(typeof(TAttribute), inherit); 156 | } 157 | } 158 | } 159 | #endif -------------------------------------------------------------------------------- /[Main]/CharExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using JetBrains.Annotations; 4 | using Contracts = System.Diagnostics.Contracts; 5 | 6 | using static AshMind.Extensions.Internal.ObsoleteMessages; 7 | 8 | namespace AshMind.Extensions { 9 | 10 | /// 11 | /// Provides a set of extension methods for operations on Char. 12 | /// 13 | public static class CharExtensions { 14 | /// 15 | /// Indicates whether the specified Unicode character is categorized as a Unicode letter. 16 | /// 17 | /// A Unicode character. 18 | /// 19 | /// true if is a letter; otherwise, false. 20 | /// 21 | /// 22 | [Contracts.Pure] [Pure] 23 | [Obsolete(MethodWillBeRemovedInVersion4StaticConsistency)] 24 | public static bool IsLetter(this char c) { 25 | return Char.IsLetter(c); 26 | } 27 | 28 | /// 29 | /// Indicates whether the specified Unicode character is categorized as a decimal digit. 30 | /// 31 | /// A Unicode character. 32 | /// 33 | /// true if is a decimal digit; otherwise, false. 34 | /// 35 | /// 36 | [Contracts.Pure] [Pure] 37 | [Obsolete(MethodWillBeRemovedInVersion4StaticConsistency)] 38 | public static bool IsDigit(this char c) { 39 | return Char.IsDigit(c); 40 | } 41 | 42 | /// 43 | /// Indicates whether the specified Unicode character is categorized as a letter or a decimal digit. 44 | /// 45 | /// A Unicode character. 46 | /// 47 | /// true if is a letter or a decimal digit; otherwise, false. 48 | /// 49 | /// 50 | [Contracts.Pure] [Pure] 51 | [Obsolete(MethodWillBeRemovedInVersion4StaticConsistency)] 52 | public static bool IsLetterOrDigit(this char c) { 53 | return Char.IsLetterOrDigit(c); 54 | } 55 | 56 | /// 57 | /// Indicates whether the specified Unicode character is categorized as a number. 58 | /// 59 | /// A Unicode character. 60 | /// 61 | /// true if is a number; otherwise, false. 62 | /// 63 | /// 64 | [Contracts.Pure] [Pure] 65 | [Obsolete(MethodWillBeRemovedInVersion4StaticConsistency)] 66 | public static bool IsNumber(this char c) { 67 | return Char.IsNumber(c); 68 | } 69 | 70 | /// 71 | /// Indicates whether the specified Unicode character is categorized as a symbol character. 72 | /// 73 | /// A Unicode character. 74 | /// 75 | /// true if is a symbol character; otherwise, false. 76 | /// 77 | /// 78 | [Contracts.Pure] [Pure] 79 | [Obsolete(MethodWillBeRemovedInVersion4StaticConsistency)] 80 | public static bool IsSymbol(this char c) { 81 | return Char.IsSymbol(c); 82 | } 83 | 84 | /// 85 | /// Indicates whether the specified Unicode character is categorized as a control character. 86 | /// 87 | /// A Unicode character. 88 | /// 89 | /// true if is a control character; otherwise, false. 90 | /// 91 | /// 92 | [Contracts.Pure] [Pure] 93 | [Obsolete(MethodWillBeRemovedInVersion4StaticConsistency)] 94 | public static bool IsControl(this char c) { 95 | return Char.IsControl(c); 96 | } 97 | 98 | /// 99 | /// Indicates whether the specified Unicode character is categorized as a punctuation mark. 100 | /// 101 | /// A Unicode character. 102 | /// 103 | /// true if is a punctuation mark; otherwise, false. 104 | /// 105 | /// 106 | [Contracts.Pure] [Pure] 107 | [Obsolete(MethodWillBeRemovedInVersion4StaticConsistency)] 108 | public static bool IsPunctuation(this char c) { 109 | return Char.IsPunctuation(c); 110 | } 111 | 112 | /// 113 | /// Indicates whether the specified Unicode character is categorized as a separator character. 114 | /// 115 | /// A Unicode character. 116 | /// 117 | /// true if is a separator character; otherwise, false. 118 | /// 119 | /// 120 | [Contracts.Pure] [Pure] 121 | [Obsolete(MethodWillBeRemovedInVersion4StaticConsistency)] 122 | public static bool IsSeparator(this char c) { 123 | return Char.IsSeparator(c); 124 | } 125 | 126 | /// 127 | /// Indicates whether the specified character has a surrogate code point. 128 | /// 129 | /// A Unicode character. 130 | /// 131 | /// true if is a high surrogate or a low surrogate; otherwise, false. 132 | /// 133 | /// 134 | /// 135 | /// 136 | [Contracts.Pure] [Pure] 137 | [Obsolete(MethodWillBeRemovedInVersion4StaticConsistency)] 138 | public static bool IsSurrogate(this char c) { 139 | return Char.IsSurrogate(c); 140 | } 141 | 142 | /// 143 | /// Indicates whether the specified object is a high surrogate. 144 | /// 145 | /// A character. 146 | /// 147 | /// true if the numeric value of the parameter ranges from U+D800 through U+DBFF; otherwise, false. 148 | /// 149 | /// 150 | [Contracts.Pure] [Pure] 151 | [Obsolete(MethodWillBeRemovedInVersion4StaticConsistency)] 152 | public static bool IsHighSurrogate(this char c) { 153 | return Char.IsHighSurrogate(c); 154 | } 155 | 156 | /// 157 | /// Indicates whether the specified object is a low surrogate. 158 | /// 159 | /// A character. 160 | /// 161 | /// true if the numeric value of the parameter ranges from U+DC00 through U+DFFF; otherwise, false. 162 | /// 163 | /// 164 | [Contracts.Pure] [Pure] 165 | [Obsolete(MethodWillBeRemovedInVersion4StaticConsistency)] 166 | public static bool IsLowSurrogate(this char c) { 167 | return Char.IsLowSurrogate(c); 168 | } 169 | 170 | /// 171 | /// Indicates whether the specified Unicode character is categorized as white space. 172 | /// 173 | /// A Unicode character. 174 | /// 175 | /// true if is white space; otherwise, false. 176 | /// 177 | /// 178 | [Contracts.Pure] [Pure] 179 | [Obsolete(MethodWillBeRemovedInVersion4StaticConsistency)] 180 | public static bool IsWhiteSpace(this char c) { 181 | return Char.IsWhiteSpace(c); 182 | } 183 | 184 | /// 185 | /// Indicates whether the specified Unicode character is categorized as a lowercase letter. 186 | /// 187 | /// A Unicode character. 188 | /// 189 | /// true if is lowercase letter; otherwise, false. 190 | /// 191 | /// 192 | [Contracts.Pure] [Pure] 193 | [Obsolete(MethodWillBeRemovedInVersion4StaticConsistency)] 194 | public static bool IsLower(this char c) { 195 | return Char.IsLower(c); 196 | } 197 | 198 | /// 199 | /// Indicates whether the specified Unicode character is categorized as a uppercase letter. 200 | /// 201 | /// A Unicode character. 202 | /// 203 | /// true if is uppercase letter; otherwise, false. 204 | /// 205 | /// 206 | [Contracts.Pure] [Pure] 207 | [Obsolete(MethodWillBeRemovedInVersion4StaticConsistency)] 208 | public static bool IsUpper(this char c) { 209 | return Char.IsUpper(c); 210 | } 211 | 212 | /// 213 | /// Converts the value of a Unicode character to its lowercase equivalent. 214 | /// 215 | /// A Unicode character. 216 | /// 217 | /// The lowercase equivalent of , or the unchanged value of , if is already lowercase or not alphabetic. 218 | /// 219 | /// 220 | [Contracts.Pure] [Pure] 221 | [Obsolete(MethodWillBeRemovedInVersion4StaticConsistency)] 222 | public static char ToLower(this char c) { 223 | return Char.ToLower(c); 224 | } 225 | 226 | #if !No_Char_ToUpperOrLower_Culture 227 | /// 228 | /// Converts the value of a specified Unicode character to its lowercase equivalent using specified culture-specific formatting information. 229 | /// 230 | /// A Unicode character. 231 | /// 232 | /// A object that supplies culture-specific casing rules, or a null reference (Nothing in Visual Basic). 233 | /// 234 | /// 235 | /// The lowercase equivalent of , modified according to , or the unchanged value of , if is already lowercase or not alphabetic. 236 | /// 237 | /// 238 | [Contracts.Pure] [Pure] 239 | [Obsolete(MethodWillBeRemovedInVersion4StaticConsistency)] 240 | public static char ToLower(this char c, [NotNull] CultureInfo culture) { 241 | return Char.ToLower(c, culture); 242 | } 243 | #endif 244 | 245 | /// 246 | /// Converts the value of a Unicode character to its lowercase equivalent using the casing rules of the invariant culture. 247 | /// 248 | /// A Unicode character. 249 | /// 250 | /// The lowercase equivalent of , or the unchanged value of , if is already lowercase or not alphabetic. 251 | /// 252 | /// 253 | [Contracts.Pure] [Pure] 254 | [Obsolete(MethodWillBeRemovedInVersion4StaticConsistency)] 255 | public static char ToLowerInvariant(this char c) { 256 | return Char.ToLowerInvariant(c); 257 | } 258 | 259 | /// 260 | /// Converts the value of a Unicode character to its uppercase equivalent. 261 | /// 262 | /// A Unicode character. 263 | /// 264 | /// The uppercase equivalent of , or the unchanged value of , if is already lowercase or not alphabetic. 265 | /// 266 | /// 267 | [Contracts.Pure] [Pure] 268 | [Obsolete(MethodWillBeRemovedInVersion4StaticConsistency)] 269 | public static char ToUpper(this char c) { 270 | return Char.ToUpper(c); 271 | } 272 | 273 | #if !No_Char_ToUpperOrLower_Culture 274 | /// 275 | /// Converts the value of a specified Unicode character to its uppercase equivalent using specified culture-specific formatting information. 276 | /// 277 | /// A Unicode character. 278 | /// 279 | /// A object that supplies culture-specific casing rules, or a null reference (Nothing in Visual Basic). 280 | /// 281 | /// 282 | /// The uppercase equivalent of , modified according to , or the unchanged value of , if is already lowercase or not alphabetic. 283 | /// 284 | /// 285 | [Contracts.Pure] [Pure] 286 | [Obsolete(MethodWillBeRemovedInVersion4StaticConsistency)] 287 | public static char ToUpper(this char c, [NotNull] CultureInfo culture) { 288 | return Char.ToUpper(c, culture); 289 | } 290 | #endif 291 | 292 | /// 293 | /// Converts the value of a Unicode character to its uppercase equivalent using the casing rules of the invariant culture. 294 | /// 295 | /// A Unicode character. 296 | /// 297 | /// The uppercase equivalent of , or the unchanged value of , if is already lowercase or not alphabetic. 298 | /// 299 | /// 300 | [Contracts.Pure] [Pure] 301 | [Obsolete(MethodWillBeRemovedInVersion4StaticConsistency)] 302 | public static char ToUpperInvariant(this char c) { 303 | return Char.ToUpperInvariant(c); 304 | } 305 | } 306 | } 307 | -------------------------------------------------------------------------------- /[Main]/ArrayExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using JetBrains.Annotations; 4 | using Contracts = System.Diagnostics.Contracts; 5 | 6 | namespace AshMind.Extensions { 7 | /// 8 | /// Provides a set of extension methods for operations on . 9 | /// 10 | public static class ArrayExtensions { 11 | /// 12 | /// Searches for the specified object and returns the index of the first occurrence within the 13 | /// entire . 14 | /// 15 | /// 16 | /// The type of the elements of the array. 17 | /// The one-dimensional, zero-based to search. 18 | /// The object to locate in . 19 | /// 20 | /// 21 | /// The zero-based index of the first occurrence of within 22 | /// the entire , if found; otherwise, –1. 23 | /// 24 | /// 25 | /// is null. 26 | [Contracts.Pure] [Pure] 27 | public static int IndexOf([NotNull] this T[] array, [CanBeNull] T value) { 28 | return Array.IndexOf(array, value); 29 | } 30 | 31 | /// 32 | /// Searches for the specified object and returns the index of the first occurrence within the range of elements 33 | /// in the that extends from the specified index to the last element. 34 | /// 35 | /// 36 | /// The type of the elements of the array. 37 | /// The one-dimensional, zero-based to search. 38 | /// The object to locate in . 39 | /// The zero-based starting index of the search. 40 | /// 41 | /// 42 | /// The zero-based index of the first occurrence of within the range of elements 43 | /// in that extends from to the last element, if found; otherwise, –1. 44 | /// 45 | /// 46 | /// is null. 47 | /// 48 | /// is outside the range of valid indexes for . 49 | /// 50 | [Contracts.Pure] [Pure] 51 | public static int IndexOf([NotNull] this T[] array, [CanBeNull] T value, int startIndex) { 52 | return Array.IndexOf(array, value, startIndex); 53 | } 54 | 55 | /// 56 | /// Searches for the specified object and returns the index of the first occurrence within the range of elements 57 | /// in the that starts at the specified index and contains the specified number of elements. 58 | /// 59 | /// 60 | /// The type of the elements of the array. 61 | /// The one-dimensional, zero-based to search. 62 | /// The object to locate in . 63 | /// The zero-based starting index of the search. 64 | /// The number of elements in the section to search. 65 | /// 66 | /// 67 | /// The zero-based index of the first occurrence of within the range of elements in 68 | /// that starts at and contains the number of elements 69 | /// specified in , if found; otherwise, –1. 70 | /// 71 | /// 72 | /// is null. 73 | /// 74 | /// is outside the range of valid indexes for . 75 | /// -or- 76 | /// is less than zero. 77 | /// -or- 78 | /// and do not specify a valid section in . 79 | /// 80 | [Contracts.Pure] [Pure] 81 | public static int IndexOf([NotNull] this T[] array, [CanBeNull] T value, int startIndex, int count) { 82 | return Array.IndexOf(array, value, startIndex, count); 83 | } 84 | 85 | /// 86 | /// Searches for the specified object and returns the index of the last occurrence 87 | /// within the entire . 88 | /// 89 | /// 90 | /// The type of the elements of the array. 91 | /// The one-dimensional, zero-based to search. 92 | /// The object to locate in . 93 | /// 94 | /// 95 | /// The zero-based index of the last occurrence of within the 96 | /// entire , if found; otherwise, –1. 97 | /// 98 | /// 99 | /// is null. 100 | [Contracts.Pure] [Pure] 101 | public static int LastIndexOf([NotNull] this T[] array, [CanBeNull] T value) { 102 | return Array.LastIndexOf(array, value); 103 | } 104 | 105 | /// 106 | /// Searches for the specified object and returns the index of the last occurrence within the range of elements in the 107 | /// that extends from the first element to the specified index. 108 | /// 109 | /// 110 | /// The type of the elements of the array. 111 | /// The one-dimensional, zero-based to search. 112 | /// The object to locate in . 113 | /// The zero-based starting index of the backward search. 114 | /// 115 | /// 116 | /// The zero-based index of the last occurrence of within the range of elements in 117 | /// that extends from the first element to , if found; 118 | /// otherwise, –1. 119 | /// 120 | /// 121 | /// is null. 122 | /// 123 | /// is outside the range of valid indexes for . 124 | /// 125 | [Contracts.Pure] [Pure] 126 | public static int LastIndexOf([NotNull] this T[] array, [CanBeNull] T value, int startIndex) { 127 | return Array.LastIndexOf(array, value, startIndex); 128 | } 129 | 130 | /// 131 | /// Searches for the specified object and returns the index of the last occurrence within the range of elements 132 | /// in the that contains the specified number of elements and ends at the specified index. 133 | /// 134 | /// 135 | /// The type of the elements of the array. 136 | /// The one-dimensional, zero-based to search. 137 | /// The object to locate in . 138 | /// The zero-based starting index of the backward search. 139 | /// The number of elements in the section to search. 140 | /// 141 | /// 142 | /// The zero-based index of the last occurrence of within the range of elements 143 | /// in that contains the number of elements specified in and 144 | /// ends at , if found; otherwise, –1. 145 | /// 146 | /// 147 | /// is null. 148 | /// 149 | /// is outside the range of valid indexes for . 150 | /// -or- 151 | /// is less than zero. 152 | /// -or- 153 | /// and do not specify a valid section in . 154 | /// 155 | [Contracts.Pure] [Pure] 156 | public static int LastIndexOf([NotNull] this T[] array, [CanBeNull] T value, int startIndex, int count) { 157 | return Array.LastIndexOf(array, value, startIndex, count); 158 | } 159 | 160 | /// 161 | /// Reverses the sequence of the elements in the entire one-dimensional . 162 | /// 163 | /// The type of the elements of the array. 164 | /// The one-dimensional to reverse. 165 | /// is null. 166 | /// is multidimensional. 167 | public static void Reverse([NotNull] this T[] array) { 168 | Array.Reverse(array); 169 | } 170 | 171 | /// 172 | /// Reverses the sequence of the elements in the entire one-dimensional . 173 | /// 174 | /// The type of the elements of the array. 175 | /// The one-dimensional to reverse. 176 | /// The starting index of the section to reverse. 177 | /// The number of elements in the section to reverse. 178 | /// is null. 179 | /// is multidimensional. 180 | /// 181 | /// is less than the lower bound of . 182 | /// -or- 183 | /// is less than zero. 184 | /// 185 | public static void Reverse([NotNull] this T[] array, int index, int length) { 186 | Array.Reverse(array, index, length); 187 | } 188 | 189 | /// 190 | /// Sorts the elements in an entire using the generic interface implementation 191 | /// of each element of the . 192 | /// 193 | /// The type of the elements of the array. 194 | /// The one-dimensional, zero-based to sort. 195 | /// is null. 196 | /// 197 | /// One or more elements in do not implement the generic interface. 198 | /// 199 | public static void Sort([NotNull] this T[] array) { 200 | Array.Sort(array); 201 | } 202 | 203 | /// 204 | /// Sorts the elements in an entire using the the specified . 205 | /// 206 | /// 207 | /// The type of the elements of the array. 208 | /// The one-dimensional, zero-based to sort. 209 | /// The to use when comparing elements. 210 | /// 211 | /// 212 | /// is null. 213 | /// -or- 214 | /// is null. 215 | /// 216 | /// 217 | /// The implementation of caused an error during the sort. 218 | /// For example, might not return 0 when comparing an item with itself. 219 | /// 220 | public static void Sort([NotNull] this T[] array, [NotNull] [InstantHandle] Comparison comparison) { 221 | Array.Sort(array, comparison); 222 | } 223 | 224 | /// 225 | /// Sorts the elements in an entire using the specified 226 | /// generic interface implementation. 227 | /// 228 | /// 229 | /// The type of the elements of the array. 230 | /// The one-dimensional, zero-based to sort. 231 | /// 232 | /// The generic interface implementation to use when comparing 233 | /// elements, or null to use the generic interface implementation of each element. 234 | /// 235 | /// 236 | /// is null. 237 | /// 238 | /// is null, and one or more elements in 239 | /// do not implement the generic interface. 240 | /// 241 | /// 242 | /// The implementation of caused an error during the sort. 243 | /// For example, might not return 0 when comparing an item with itself. 244 | /// 245 | public static void Sort([NotNull] this T[] array, [NotNull] IComparer comparer) { 246 | Array.Sort(array, comparer); 247 | } 248 | 249 | /// 250 | /// Sorts the elements in a range of elements in an using the 251 | /// generic interface implementation of each element of the . 252 | /// 253 | /// 254 | /// The type of the elements of the array. 255 | /// The one-dimensional, zero-based to sort. 256 | /// The starting index of the range to sort. 257 | /// The number of elements in the range to sort. 258 | /// 259 | /// is null. 260 | /// 261 | /// is less than the lower bound of . 262 | /// -or- 263 | /// is less than zero. 264 | /// 265 | /// 266 | /// and do not specify a valid range in . 267 | /// 268 | /// 269 | /// One or more elements in do not implement the generic interface. 270 | /// 271 | public static void Sort([NotNull] this T[] array, int index, int length) { 272 | Array.Sort(array, index, length); 273 | } 274 | 275 | /// 276 | /// Sorts the elements in a range of elements in an using the specified 277 | /// generic interface implementation. 278 | /// 279 | /// 280 | /// The type of the elements of the array. 281 | /// The one-dimensional, zero-based to sort. 282 | /// The starting index of the range to sort. 283 | /// The number of elements in the range to sort. 284 | /// 285 | /// The generic interface implementation to use when comparing 286 | /// elements, or null to use the generic interface implementation of each element. 287 | /// 288 | /// 289 | /// is null. 290 | /// 291 | /// is less than the lower bound of . 292 | /// -or- 293 | /// is less than zero. 294 | /// 295 | /// 296 | /// and do not specify a valid range in . 297 | /// -or- 298 | /// The implementation of caused an error during the sort. For example, 299 | /// might not return 0 when comparing an item with itself. 300 | /// 301 | /// 302 | /// is null, and one or more elements in 303 | /// do not implement the generic interface. 304 | /// 305 | public static void Sort([NotNull] this T[] array, int index, int length, [NotNull] IComparer comparer) { 306 | Array.Sort(array, index, length, comparer); 307 | } 308 | } 309 | } 310 | -------------------------------------------------------------------------------- /[Main]/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.Contracts; 3 | using JetBrains.Annotations; 4 | using PureAttribute = JetBrains.Annotations.PureAttribute; 5 | using Contracts = System.Diagnostics.Contracts; 6 | 7 | using static AshMind.Extensions.Internal.ObsoleteMessages; 8 | 9 | namespace AshMind.Extensions { 10 | /// 11 | /// Provides a set of extension methods for operations on String. 12 | /// 13 | public static class StringExtensions { 14 | /// Indicates whether the specified string is null or an Empty string. 15 | /// The string to test. 16 | /// 17 | /// true if the is null or an empty string (""); otherwise, false. 18 | /// 19 | [Contracts.Pure] [Pure] 20 | [ContractAnnotation("value:null=>true")] 21 | [Obsolete(MethodWillBeRemovedInVersion4StaticConsistency)] 22 | public static bool IsNullOrEmpty([CanBeNull] this string? value) { 23 | return string.IsNullOrEmpty(value); 24 | } 25 | 26 | /// Return the specified string if it is not Empty, or null otherwise. 27 | /// The string to test. 28 | /// 29 | /// var displayName = name.NullIfEmpty() ?? "Unknown"; 30 | /// 31 | /// 32 | /// if it is an empty string (""); otherwise, null. 33 | /// 34 | [Contracts.Pure] [Pure] [CanBeNull] 35 | [ContractAnnotation("value:null=>null")] 36 | public static string? NullIfEmpty([CanBeNull] this string? value) { 37 | return !string.IsNullOrEmpty(value) ? value : null; 38 | } 39 | 40 | /// 41 | /// Indicates whether a specified string is null, empty, or consists only of white-space characters. 42 | /// 43 | /// A value. 44 | /// 45 | /// true if the value parameter is null or , or if consists exclusively of white-space characters. 46 | /// 47 | /// 48 | [Contracts.Pure] [Pure] 49 | [ContractAnnotation("value:null=>true")] 50 | [Obsolete(MethodWillBeRemovedInVersion4StaticConsistency)] 51 | public static bool IsNullOrWhiteSpace([CanBeNull] this string? value) { 52 | return string.IsNullOrWhiteSpace(value); 53 | } 54 | 55 | /// 56 | /// Returns a string array that contains the substrings in this string that are delimited by a specified string. 57 | /// 58 | /// A value to split. 59 | /// An string that delimits the substrings in this string, or a null reference (Nothing in Visual Basic). 60 | /// 61 | /// An array whose elements contain the substrings in this string that are delimited by . 62 | /// 63 | /// 64 | [Contracts.Pure] [Pure] [NotNull] 65 | public static string[] Split([NotNull] this string value, [CanBeNull] string? separator) { 66 | return value.Split(separator, StringSplitOptions.None); 67 | } 68 | 69 | /// 70 | /// Returns a string array that contains the substrings in this string that are delimited by elements of a specified string array. 71 | /// 72 | /// A value to split. 73 | /// An array of strings that delimit the substrings in this string, an empty array that contains no delimiters, or a null reference (Nothing in Visual Basic). 74 | /// 75 | /// An array whose elements contain the substrings in this string that are delimited by one or more strings in . 76 | /// 77 | /// 78 | [Contracts.Pure] [Pure] [NotNull] 79 | public static string[] Split([NotNull] this string value, [CanBeNull] params string[]? separator) { 80 | return value.Split(separator, StringSplitOptions.None); 81 | } 82 | 83 | /// 84 | /// Returns a string array that contains the substrings in this string that are delimited by a specified string. 85 | /// 86 | /// A value to split. 87 | /// An string that delimits the substrings in this string, or a null reference (Nothing in Visual Basic). 88 | /// to omit empty array elements from the array returned; or to include empty array elements in the array returned. 89 | /// 90 | /// An array whose elements contain the substrings in this string that are delimited by . 91 | /// 92 | /// 93 | [Contracts.Pure] [Pure] [NotNull] 94 | public static string[] Split([NotNull] this string value, [CanBeNull] string? separator, StringSplitOptions options) { 95 | var separators = separator != null ? new[] { separator } : new string[0]; 96 | return value.Split(separators, options); 97 | } 98 | 99 | /// 100 | /// Returns a value indicating whether the specified String object occurs within this string. 101 | /// 102 | /// The value to be analyzed. 103 | /// The object to seek. 104 | /// One of the values that determines how this string and value are compared. 105 | /// 106 | /// true if the value parameter occurs within this string, or if value is the empty string (""); otherwise, false. 107 | /// 108 | [Contracts.Pure] [Pure] 109 | public static bool Contains([NotNull] this string original, [NotNull] string value, StringComparison comparisonType) { 110 | return original.IndexOf(value, comparisonType) >= 0; 111 | } 112 | 113 | /// 114 | /// Returns a substring preceding the first occurrence of a specified value. 115 | /// 116 | /// The value to get substring from. 117 | /// The value following the substring. 118 | /// 119 | /// Substring preceding the first occurrence of , if found; otherwise, the string. 120 | /// 121 | [Contracts.Pure] [Pure] [NotNull] 122 | public static string SubstringBefore([NotNull] this string original, [NotNull] string value) { 123 | // ReSharper disable once StringIndexOfIsCultureSpecific.1 124 | return original.SubstringBefore(original.IndexOf(value)); 125 | } 126 | 127 | /// 128 | /// Returns a substring preceding the first occurrence of a specified value. 129 | /// 130 | /// The value to get substring from. 131 | /// The value following the substring. 132 | /// One of the values that determines how and are compared. 133 | /// 134 | /// Substring preceding the first occurrence of , if found; otherwise, the string. 135 | /// 136 | [Contracts.Pure] [Pure] [NotNull] 137 | public static string SubstringBefore([NotNull] this string original, [NotNull] string value, StringComparison comparisonType) { 138 | return original.SubstringBefore(original.IndexOf(value, comparisonType)); 139 | } 140 | 141 | /// 142 | /// Returns a substring before the last occurrence of a specified value. 143 | /// 144 | /// The value to get substring from. 145 | /// The value following the substring. 146 | /// 147 | /// Substring before the last occurrence of , if found; otherwise, the string. 148 | /// 149 | [Contracts.Pure] [Pure] [NotNull] 150 | public static string SubstringBeforeLast([NotNull] this string original, [NotNull] string value) { 151 | // ReSharper disable once StringLastIndexOfIsCultureSpecific.1 152 | return original.SubstringBefore(original.LastIndexOf(value)); 153 | } 154 | 155 | /// 156 | /// Returns a substring preceding the last occurrence of a specified value. 157 | /// 158 | /// The value to get substring from. 159 | /// The value following the substring. 160 | /// One of the values that determines how and are compared. 161 | /// 162 | /// Substring preceding the last occurrence of , if found; otherwise, the string. 163 | /// 164 | [Contracts.Pure] [Pure] [NotNull] 165 | public static string SubstringBeforeLast([NotNull] this string original, [NotNull] string value, StringComparison comparisonType) { 166 | return original.SubstringBefore(original.LastIndexOf(value, comparisonType)); 167 | } 168 | 169 | [NotNull] 170 | private static string SubstringBefore([NotNull] this string original, int index) { 171 | if (index < 0) 172 | return original; 173 | 174 | return original.Substring(0, index); 175 | } 176 | 177 | /// 178 | /// Returns a substring following the first occurrence of a specified value. 179 | /// 180 | /// The value to get substring from. 181 | /// The value preceding the substring. 182 | /// 183 | /// Substring following the first occurrence of , if found; otherwise, the string. 184 | /// 185 | [Contracts.Pure] [Pure] [NotNull] 186 | public static string SubstringAfter([NotNull] this string original, [NotNull] string value) { 187 | // ReSharper disable once StringIndexOfIsCultureSpecific.1 188 | return original.SubstringAfter(original.IndexOf(value), value.Length); 189 | } 190 | 191 | /// 192 | /// Returns a substring following the first occurrence of a specified value. 193 | /// 194 | /// The value to get substring from. 195 | /// The value preceding the substring. 196 | /// One of the values that determines how and are compared. 197 | /// 198 | /// Substring following the first occurrence of , if found; otherwise, the string. 199 | /// 200 | [Contracts.Pure] [Pure] [NotNull] 201 | public static string SubstringAfter([NotNull] this string original, [NotNull] string value, StringComparison comparisonType) { 202 | return original.SubstringAfter(original.IndexOf(value, comparisonType), value.Length); 203 | } 204 | 205 | /// 206 | /// Returns a substring following the last occurrence of a specified value. 207 | /// 208 | /// The value to get substring from. 209 | /// The value preceding the substring. 210 | /// 211 | /// Substring following the last occurrence of , if found; otherwise, the string. 212 | /// 213 | [Contracts.Pure] [Pure] [NotNull] 214 | public static string SubstringAfterLast([NotNull] this string original, [NotNull] string value) { 215 | // ReSharper disable once StringLastIndexOfIsCultureSpecific.1 216 | return original.SubstringAfter(original.LastIndexOf(value), value.Length); 217 | } 218 | 219 | /// 220 | /// Returns a substring following the last occurrence of a specified value. 221 | /// 222 | /// The value to get substring from. 223 | /// The value preceding the substring. 224 | /// One of the values that determines how and are compared. 225 | /// 226 | /// Substring following the last occurrence of , if found; otherwise, the string. 227 | /// 228 | [Contracts.Pure] [Pure] [NotNull] 229 | public static string SubstringAfterLast([NotNull] this string original, [NotNull] string value, StringComparison comparisonType) { 230 | return original.SubstringAfter(original.LastIndexOf(value, comparisonType), value.Length); 231 | } 232 | 233 | [NotNull] 234 | private static string SubstringAfter([NotNull] this string original, int index, int offset) { 235 | if (index < 0) 236 | return original; 237 | 238 | return original.Substring(index + offset); 239 | } 240 | 241 | /// 242 | /// Removes a leading occurrence of the specified value, if present. 243 | /// 244 | /// The value to remove from. 245 | /// The value to be removed if present. 246 | /// 247 | /// The string that remains after an occurrence of is removed from the start of string. 248 | /// 249 | [Contracts.Pure] [Pure] [NotNull] 250 | public static string RemoveStart([NotNull] this string original, [NotNull] string prefix) { 251 | if (!original.StartsWith(prefix)) 252 | return original; 253 | 254 | return original.Substring(prefix.Length); 255 | } 256 | 257 | /// 258 | /// Removes a leading occurrence of the specified value, if present. 259 | /// 260 | /// The value to remove from. 261 | /// The value to be removed if present. 262 | /// The value that defines how and are compared. 263 | /// 264 | /// The string that remains after an occurrence of is removed from the start of string. 265 | /// 266 | [Contracts.Pure] [Pure] [NotNull] 267 | public static string RemoveStart([NotNull] this string original, [NotNull] string prefix, StringComparison comparison) { 268 | if (!original.StartsWith(prefix, comparison)) 269 | return original; 270 | 271 | return original.Substring(prefix.Length); 272 | } 273 | 274 | /// 275 | /// Removes a trailing occurrence of the specified value, if present. 276 | /// 277 | /// The value to remove from. 278 | /// The value to be removed if present. 279 | /// 280 | /// The string that remains after an occurrence of is removed from the end of string. 281 | /// 282 | [Contracts.Pure] [Pure] [NotNull] 283 | public static string RemoveEnd([NotNull] this string original, [NotNull] string suffix) { 284 | if (!original.EndsWith(suffix)) 285 | return original; 286 | 287 | return original.Substring(0, original.Length - suffix.Length); 288 | } 289 | 290 | /// 291 | /// Removes a trailing occurrence of the specified value, if present. The strings are compared using the specified comparison option. 292 | /// 293 | /// The value to remove from. 294 | /// The value to be removed if present. 295 | /// The value that defines how and are compared. 296 | /// 297 | /// The string that remains after an occurrence of is removed from the end of string. 298 | /// 299 | [Contracts.Pure] [Pure] [NotNull] 300 | public static string RemoveEnd([NotNull] this string original, [NotNull] string suffix, StringComparison comparison) { 301 | if (!original.EndsWith(suffix, comparison)) 302 | return original; 303 | 304 | return original.Substring(0, original.Length - suffix.Length); 305 | } 306 | 307 | /// 308 | /// Limits string length to a specified value by discarding any trailing characters after the specified length. 309 | /// 310 | /// The value to limit to a specified length. 311 | /// The maximum length allowed for . 312 | /// The string if its length is lesser or equal than 313 | /// ; otherwise first n characters of the 314 | /// , where n is equal to . 315 | [Contracts.Pure] [Pure] [NotNull] 316 | public static string TruncateEnd([NotNull] this string original, int maxLength) { 317 | if (original == null) throw new ArgumentNullException(nameof(original)); 318 | if (maxLength < 0) throw new ArgumentOutOfRangeException(nameof(maxLength)); 319 | Contract.EndContractBlock(); 320 | 321 | return original.Length <= maxLength ? original : original.Substring(0, maxLength); 322 | } 323 | 324 | /// 325 | /// Limits string length to a specified value by discarding a number of trailing characters and adds a specified 326 | /// suffix if any characters were discarded. 327 | /// 328 | /// The value to limit to a specified length. 329 | /// The maximum length allowed for . 330 | /// The suffix added to the result if any characters are discarded. 331 | /// The string if its length is lesser or equal than 332 | /// ; otherwise first n characters of the 333 | /// followed by , where n is equal to - length of 334 | /// . 335 | [Contracts.Pure] [Pure] [NotNull] 336 | public static string TruncateEnd([NotNull] this string original, int maxLength, [CanBeNull] string? suffix) { 337 | if (original == null) throw new ArgumentNullException(nameof(original)); 338 | if (maxLength < 0) throw new ArgumentOutOfRangeException(nameof(maxLength)); 339 | Contract.EndContractBlock(); 340 | 341 | if (original.Length <= maxLength) 342 | return original; 343 | 344 | if (suffix == null) 345 | suffix = ""; 346 | 347 | var length = maxLength - suffix.Length; 348 | if (length < 0) 349 | return suffix.Substring(0, maxLength); 350 | 351 | return original.Substring(0, length) + suffix; 352 | } 353 | } 354 | } --------------------------------------------------------------------------------