/// <summary> /// Compares substrings of two specified strings using the specified comparison rules, /// and returns an integer that indicates their relative position in the sort order. /// </summary> /// <param name="strA">The first string to use in the comparison.</param> /// <param name="indexA">The zero-based starting character position of the substring within <paramref name="strA"/>.</param> /// <param name="lengthA">The number of characters constituting the substring from <paramref name="strA"/>.</param> /// <param name="strB">The second string to use in the comparison.</param> /// <param name="indexB">The zero-based starting character position of the substring within <paramref name="strB"/>.</param> /// <param name="lengthB">The number of characters constituting the substring from <paramref name="strB"/>.</param> /// <param name="comparisonType">One of the enumeration values that specifies the rules to use in the comparison.</param> /// <returns> /// A signed integer that indicates the lexical relationship between the two comparands. /// <list type="table"> /// <listheader> /// <term>Value</term> /// <term>Condition</term> /// </listheader> /// <item> /// <term>Less than zero</term> /// <term>The substring in <paramref name="strA"/> is less than the substring in <paramref name="strB"/>.</term> /// </item> /// <item> /// <term>Zero</term> /// <term>The substrings are equal, or <paramref name="lengthA"/> and <paramref name="lengthB"/> are both zero.</term> /// </item> /// <item> /// <term>Greater than zero</term> /// <term>The substring in <paramref name="strA"/> is greater than the substring in <paramref name="strB"/>.</term> /// </item> /// </list> /// </returns> /// <remarks> /// <para> /// This method is similar to the <see cref="string.Compare(string, int, string, int, int, StringComparison)"/> method /// in the .NET Framework Class Library, but allows different lengths to be specified for the two substrings. /// It is implemented by calling the <see cref="CompareInfo.Compare(string, int, int, string, int, int, CompareOptions)"/> method /// on the appropriate <see cref="CompareInfo"/> instance with the appropriate <see cref="CompareOptions"/> value /// for each known value of <paramref name="comparisonType"/>. /// For performance, substring instantiation is avoided, working with the start indexes and lengths instead. /// </para> /// <para> /// The implementation of this method is adapted from the internal implementations for /// <see cref="string.Compare(string, int, string, int, int, StringComparison)"/> /// (<see href="https://referencesource.microsoft.com/#mscorlib/system/string.cs,1ae4d07b01230bb6">source</see>) /// and <see cref="string.IndexOf(string, int, int, StringComparison)"/> /// (<see href="https://referencesource.microsoft.com/#mscorlib/system/string.cs,ef82268cfee756fe">source</see>). /// </para> /// </remarks> public static int Compare(string strA, int indexA, int lengthA, string strB, int indexB, int lengthB, StringComparison comparisonType) { ArgumentValidate.EnumDefined(comparisonType, nameof(comparisonType)); if (strA == null) { return(strB == null ? 0 : -1); } if (strB == null) { return(1); } ArgumentValidate.StringIndexLength(strA, nameof(strA), indexA, nameof(indexA), lengthA, nameof(lengthA)); ArgumentValidate.StringIndexLength(strB, nameof(strB), indexB, nameof(indexB), lengthB, nameof(lengthB)); if (lengthA == 0 && lengthB == 0) { return(0); } if (string.ReferenceEquals(strA, strB) && indexA == indexB && lengthA == lengthB) { return(0); } return(CompareInner(strA, indexA, lengthA, strB, indexB, lengthB, comparisonType)); }
/// <summary> /// Reports the zero-based index and length of the first occurrence of the specified substring in the source string. /// </summary> /// <param name="source">The source string in which to search.</param> /// <param name="substring">The substring to seek.</param> /// <param name="comparisonType">One of the enumeration values that specifies the rules for the search.</param> /// <param name="matchIndex"> /// When this method returns, contains the zero-based starting character position of the match, if found; /// or -1 if no match is found. /// If <paramref name="substring"/> is the empty string (<c>""</c>), the value will be 0. /// </param> /// <param name="matchLength"> /// When this method returns, contains the length (in characters) of the match, if found; /// or -1 if no match is found. /// If <paramref name="substring"/> is the empty string (<c>""</c>), the value will be 0. /// </param> /// <remarks> /// Refer to the remarks on the <see cref="Find(string, string, int, int, StringComparison, out int, out int)"/> overload. /// </remarks> public static void Find(this string source, string substring, StringComparison comparisonType, out int matchIndex, out int matchLength) { ArgumentValidate.NotNull(source, nameof(source)); ArgumentValidate.NotNull(substring, nameof(substring)); ArgumentValidate.EnumDefined(comparisonType, nameof(comparisonType)); FindInner(source, substring, 0, source.Length, comparisonType, out matchIndex, out matchLength); }
/// <summary> /// Returns a value indicating whether the specified substring occurs within the source string, /// using the specified string comparison for the search. /// </summary> /// <param name="source">The source string in which to search.</param> /// <param name="value">The string to seek.</param> /// <param name="comparisonType">One of the enumeration values that specifies the rules for the search.</param> /// <returns> /// <see langword="true"/> if the <paramref name="value"/> parameter occurs within the <paramref name="source"/> string, /// or if <paramref name="value"/> is the empty string (<c>""</c>); /// otherwise, <see langword="false"/>. /// </returns> /// <exception cref="ArgumentNullException"><paramref name="source"/> or <paramref name="value"/> is <see langword="null"/>.</exception> /// <exception cref="ArgumentException"><paramref name="comparisonType"/> is not a valid <see cref="StringComparison"/> value.</exception> /// <remarks> /// The built-in <see cref="string.Contains(string)"/> method performs an ordinal (case-sensitive and culture-insensitive) comparison. /// This extension method allows the comparison type to be specified. /// </remarks> public static bool Contains(this string source, string value, StringComparison comparisonType) { ArgumentValidate.NotNull(source, nameof(source)); ArgumentValidate.NotNull(value, nameof(value)); ArgumentValidate.EnumDefined(comparisonType, nameof(comparisonType)); return(source.IndexOf(value, comparisonType) >= 0); }
/// <summary> /// Reports the zero-based index and length of the first occurrence of the specified substring in the source string. /// </summary> /// <param name="source">The source string in which to search.</param> /// <param name="searchValue">The substring to seek.</param> /// <param name="comparisonType">One of the enumeration values that specifies the rules for the search.</param> /// <param name="matchIndex"> /// When this method returns, contains the zero-based starting character position of the match, if found; /// or -1 if no match is found. /// If <paramref name="searchValue"/> is the empty string (<c>""</c>), the value will be 0. /// </param> /// <param name="matchLength"> /// When this method returns, contains the length (in characters) of the match, if found; /// or -1 if no match is found. /// If <paramref name="searchValue"/> is the empty string (<c>""</c>), the value will be 0. /// </param> /// <returns> /// <see langword="true"/> if a match for <paramref name="searchValue"/> is found in the source string; /// otherwise, <see langword="false"/>. /// </returns> /// <remarks> /// Refer to the remarks on the /// <see cref="Find(string, string, int, int, StringComparison, out int, out int)"/> overload. /// </remarks> public static bool Find(this string source, string searchValue, StringComparison comparisonType, out int matchIndex, out int matchLength) { ArgumentValidate.NotNull(source, nameof(source)); ArgumentValidate.NotNull(searchValue, nameof(searchValue)); ArgumentValidate.EnumDefined(comparisonType, nameof(comparisonType)); return(FindInner(source, searchValue, 0, source.Length, comparisonType, out matchIndex, out matchLength)); }
/// <summary> /// Executes the specified asynchronous function delegate using the disposable resource, /// then disposes of the said resource by calling its <see cref="IDisposable.Dispose()"/> method. /// </summary> /// <typeparam name="TDisposable">The type of the disposable resource to use.</typeparam> /// <typeparam name="TResult">The type of the return value of the asynchronous function delegate.</typeparam> /// <param name="disposable">The disposable resource to use.</param> /// <param name="strategy"> /// The strategy for propagating or swallowing exceptions thrown by the <see cref="IDisposable.Dispose"/> method. /// </param> /// <param name="asyncFunc">The asynchronous function delegate to execute using the disposable resource.</param> /// <returns> /// A task that represents the asynchronous operation. /// The task result contains the return value of the asynchronous function delegate. /// </returns> /// <exception cref="ArgumentNullException"><paramref name="disposable"/> or <paramref name="asyncFunc"/> is <see langword="null"/>.</exception> /// <remarks> /// Refer to the remarks on the <see cref="Using{TDisposable}(TDisposable, DisposeExceptionStrategy, Action{TDisposable})"/> overload. /// </remarks> public static Task <TResult> UsingAsync <TDisposable, TResult>(this TDisposable disposable, DisposeExceptionStrategy strategy, Func <TDisposable, Task <TResult> > asyncFunc) where TDisposable : IDisposable { ArgumentValidate.NotNull(disposable, nameof(disposable)); ArgumentValidate.NotNull(asyncFunc, nameof(asyncFunc)); ArgumentValidate.EnumDefined(strategy, nameof(strategy)); return(disposable.UsingAsyncInner(strategy, asyncFunc)); }
/// <summary> /// Executes the specified asynchronous action delegate using the disposable resource, /// then disposes of the said resource by calling its <see cref="IDisposable.Dispose()"/> method. /// </summary> /// <typeparam name="TDisposable">The type of the disposable resource to use.</typeparam> /// <param name="disposable">The disposable resource to use.</param> /// <param name="strategy"> /// The strategy for propagating or swallowing exceptions thrown by the <see cref="IDisposable.Dispose"/> method. /// </param> /// <param name="asyncAction">The asynchronous action delegate to execute using the disposable resource.</param> /// <returns>A task that represents the asynchronous operation.</returns> /// <exception cref="ArgumentNullException"><paramref name="disposable"/> or <paramref name="asyncAction"/> is <see langword="null"/>.</exception> /// <remarks> /// Refer to the remarks on the <see cref="Using{TDisposable}(TDisposable, DisposeExceptionStrategy, Action{TDisposable})"/> overload. /// </remarks> public static Task UsingAsync <TDisposable>(this TDisposable disposable, DisposeExceptionStrategy strategy, Func <TDisposable, Task> asyncAction) where TDisposable : IDisposable { ArgumentValidate.NotNull(disposable, nameof(disposable)); ArgumentValidate.NotNull(asyncAction, nameof(asyncAction)); ArgumentValidate.EnumDefined(strategy, nameof(strategy)); var asyncFunc = asyncAction.ReturnAsync(true); return(disposable.UsingAsync(strategy, asyncFunc)); }
/// <summary> /// Executes the specified action delegate using the disposable resource, /// then disposes of the said resource by calling its <see cref="IDisposable.Dispose()"/> method. /// </summary> /// <typeparam name="TDisposable">The type of the disposable resource to use.</typeparam> /// <param name="disposable">The disposable resource to use.</param> /// <param name="strategy"> /// The strategy for propagating or swallowing exceptions thrown by the <see cref="IDisposable.Dispose"/> method. /// </param> /// <param name="action">The action delegate to execute using the disposable resource.</param> /// <exception cref="ArgumentNullException"><paramref name="disposable"/> or <paramref name="action"/> is <see langword="null"/>.</exception> /// <remarks> /// <para> /// This extension method enhances the functionality of the <see langword="using"/> keyword by providing alternative strategies /// for propagating or swallowing exceptions thrown by the <see cref="IDisposable.Dispose"/> method, /// in conjunction with exceptions thrown by the main logic of the <paramref name="action"/> delegate. /// </para> /// <para> /// The standard behavior of the <see langword="using"/> keyword causes any exceptions from the main logic /// to be hidden and lost if an exception is also thrown from <see cref="IDisposable.Dispose"/>. /// According to Sections 8.9.5 and 8.10 of the C# Language Specification (version 5.0): /// </para> /// <blockquote> /// If the <see langword="finally"/> block throws another exception, processing of the current exception /// [that was thrown from the <see langword="try"/> block or a <see langword="catch"/> block] is terminated. /// </blockquote> /// <blockquote> /// If an exception is thrown during execution of a <see langword="finally"/> block, /// and is not caught within the same <see langword="finally"/> block, /// the exception is propagated to the next enclosing <see langword="try"/> statement. /// If another exception was in the process of being propagated, that exception is lost. /// </blockquote> /// <para> /// MSDN cautions against the throwing of exceptions from the <see cref="IDisposable.Dispose"/> method. /// </para> /// <blockquote> /// X AVOID throwing an exception from within <see cref="IDisposable.Dispose"/> except under critical situations /// where the containing process has been corrupted (leaks, inconsistent shared state, etc.). /// Users expect that a call to <see cref="IDisposable.Dispose"/> will not raise an exception. /// </blockquote> /// <para> /// However, this guideline is broken repeatedly throughout the .NET Framework, including in the <see cref="FileStream"/> class, /// which can throw <see cref="IOException"/> whilst flushing its buffered data to the underlying device, /// and notoriously from the <see href="https://docs.microsoft.com/en-us/dotnet/api/system.servicemodel.clientbase-1">ClientBase<TChannel></see> base class for WCF clients, /// which recommends ditching the <see langword="using"/> keyword to avoid this issue /// in <see href="https://docs.microsoft.com/en-us/dotnet/framework/wcf/samples/use-close-abort-release-wcf-client-resources">Close and Abort release resources safely when network connections have dropped</see>. /// </para> /// <para> /// The problematic implications of such exception swallowing are discussed /// in this <see href="https://stackoverflow.com/q/577607/1149773">Stack Overflow question</see>: /// </para> /// <blockquote> /// <list type="number"> /// <item>A first exception is thrown</item> /// <item>A finally block is executed as a result of the first exception</item> /// <item>The finally block calls a Dispose() method</item> /// <item>The Dispose() method throws a second exception</item> /// </list> /// […] You lose information because .NET unceremoneously replaces the first exception with the second one. /// A catch block somewhere up the call stack will therefore never see the first exception. /// However, one is usually more interested in the first exception because that normally gives better clues /// as to why things started to go wrong. /// </blockquote> /// <para> /// This extension method aims to provide a conclusive resolution to this issue by implementing the various plausible strategies, /// including the standard behavior, and leaving it up to the caller to decide which to use. /// </para> /// <list type="bullet"> /// <listheader>References</listheader> /// <item><see href="https://stackoverflow.com/q/577607/1149773">Should you implement IDisposable.Dispose() so that it never throws?</see>, <i>Stack Overflow</i></item> /// <item><see href="https://stackoverflow.com/q/35602760/1149773">Delegate-parameterized method vs IDisposable implementation"</see>, <i>Stack Overflow</i></item> /// <item><see href="https://stackoverflow.com/q/1654487/1149773">Swallowing exception thrown in catch/finally block"</see>, <i>Stack Overflow</i></item> /// <item><see href="https://docs.microsoft.com/en-us/dotnet/standard/garbage-collection/implementing-dispose">Implementing a Dispose Method</see>, <i>MSDN Library</i></item> /// </list> /// </remarks> /// <example> /// <code> /// new FileStream(Path.GetTempFileName(), FileMode.Create) /// .Using(strategy: DisposeExceptionStrategy.Subjugate, action: fileStream => /// { /// // Access fileStream here /// fileStream.WriteByte(42); /// throw new InvalidOperationException(); /// }); /// // Any Dispose() exceptions will be swallowed due to the above InvalidOperationException /// </code> /// </example> public static void Using <TDisposable>(this TDisposable disposable, DisposeExceptionStrategy strategy, Action <TDisposable> action) where TDisposable : IDisposable { ArgumentValidate.NotNull(disposable, nameof(disposable)); ArgumentValidate.NotNull(action, nameof(action)); ArgumentValidate.EnumDefined(strategy, nameof(strategy)); var func = action.Return(true); disposable.Using(strategy, func); }
/// <summary> /// Executes the specified function delegate using the disposable resource, /// then disposes of the said resource by calling its <see cref="IDisposable.Dispose()"/> method. /// </summary> /// <typeparam name="TDisposable">The type of the disposable resource to use.</typeparam> /// <typeparam name="TResult">The type of the return value of the function delegate.</typeparam> /// <param name="disposable">The disposable resource to use.</param> /// <param name="strategy"> /// The strategy for propagating or swallowing exceptions thrown by the <see cref="IDisposable.Dispose"/> method. /// </param> /// <param name="func">The function delegate to execute using the disposable resource.</param> /// <returns>The return value of the function delegate.</returns> /// <exception cref="ArgumentNullException"><paramref name="disposable"/> or <paramref name="func"/> is <see langword="null"/>.</exception> /// <remarks> /// Refer to the remarks on the <see cref="Using{TDisposable}(TDisposable, DisposeExceptionStrategy, Action{TDisposable})"/> overload. /// </remarks> public static TResult Using <TDisposable, TResult>(this TDisposable disposable, DisposeExceptionStrategy strategy, Func <TDisposable, TResult> func) where TDisposable : IDisposable { ArgumentValidate.NotNull(disposable, nameof(disposable)); ArgumentValidate.NotNull(func, nameof(func)); ArgumentValidate.EnumDefined(strategy, nameof(strategy)); var asyncFunc = func.WrapAsync(); var completedTask = disposable.UsingAsync(strategy, asyncFunc); return(completedTask.GetResult()); // task is always completed; returns immediately }
public void EnumDefined() { foreach (var comparison in EnumUtility.GetValues <StringComparison>()) { ArgumentValidate.EnumDefined(comparison, nameof(comparison)); } var invalidComparison = (StringComparison)int.MaxValue; var exception = ExceptionAssert.Throws <ArgumentException>(() => ArgumentValidate.EnumDefined(invalidComparison, nameof(invalidComparison))); Assert.AreEqual(nameof(invalidComparison), exception.ParamName); }