/// <summary> /// Gets the assembly's version. /// </summary> /// <param name="assembly">The assembly to get the version from</param> /// <returns>The assembly version</returns> public static Version?GetVersion(Assembly assembly) { Conditions.RequireReference(assembly, nameof(assembly)); Version?result = assembly.GetName(false).Version; return(result); }
/// <summary> /// Creates a new XmlSchemaSet from the XElements containing the schemas using the specified handler. /// </summary> /// <param name="schemaElements">The XML elements containing the schemas.</param> /// <param name="handler">The handler used to process errors and warnings. This can be null, /// which means an XmlSchemaException will be thrown if an error occurs.</param> /// <returns>The new XmlSchemaSet instance.</returns> public static XmlSchemaSet CreateSchemaSet(IEnumerable <XElement> schemaElements, ValidationEventHandler?handler) { Conditions.RequireReference(schemaElements, nameof(schemaElements)); // Unfortunately, XmlSchema is NOT thread-safe by itself because adding it to an XmlSchemaSet // will modify it. To use it thread-safely, we have to create the XmlSchemaSet too, and even then // the resulting set is only thread-safe if it's used from a validating reader (which our Validate does). // http://blogs.msdn.com/b/marcelolr/archive/2009/03/16/xmlschema-and-xmlschemaset-thread-safety.aspx XmlSchemaSet result = new(); foreach (XElement schemaElement in schemaElements) { using (XmlReader reader = schemaElement.CreateReader()) { XmlSchema?schema = XmlSchema.Read(reader, handler); if (schema != null) { result.Add(schema); } } } // Call this up front so it doesn't have to be implicitly called later. Any time validation is done, // the schema must be compiled first. Doing it here means later threads won't have to bother. result.Compile(); return(result); }
/// <summary> /// Enumerates all of the exceptions in an exception chain including handling multiple /// inner exceptions for <see cref="AggregateException"/>. /// </summary> /// <param name="ex">The root exception to start from. <paramref name="action"/> will be called for this too.</param> /// <param name="action">The action to invoke for the root exception and each inner exception. This is passed the /// exception and its 0-based depth (where 0 is the root exception).</param> public static void ForEach(Exception ex, Action <Exception, int> action) { Conditions.RequireReference(ex, nameof(ex)); Conditions.RequireReference(action, nameof(action)); ForEach(ex, 0, null, (exception, depth, outer) => action(exception, depth)); }
/// <summary> /// Tokenize the input string using the specified separator (e.g., comma), delimiter (e.g., double quote), /// and trimming options and add the output tokens to the specified collection. /// </summary> /// <param name="text">The text to tokenize.</param> /// <param name="separator">The token separator character. Typically, a comma.</param> /// <param name="delimiter">The token quote character. Typically, a double quote. /// This can be used to enclose tokens that contain the separator character. /// To use this character inside a token, use it two consecutive times. /// </param> /// <param name="trimTokens">Whether the resulting tokens should have whitespace trimmed off.</param> /// <param name="tokens">The collection to add the parsed tokens to.</param> /// <returns>True if all of the tokens were properly delimited (or were not delimited). /// False if the final token was open-delimited but never closed (which usually indicates a partial record).</returns> public static bool SplitIntoTokens(string text, char separator, char?delimiter, bool trimTokens, ICollection <string> tokens) { Conditions.RequireReference(tokens, nameof(tokens)); bool result = SplitIntoTokens(text, separator, delimiter, token => tokens.Add(trimTokens ? token.Trim() : token)); return(result); }
/// <summary> /// Enumerates all of the exceptions in an exception chain including handling multiple /// inner exceptions for <see cref="AggregateException"/>. /// </summary> /// <param name="ex">The root exception to start from. <paramref name="action"/> will be called for this too.</param> /// <param name="action">The action to invoke for the root exception and each inner exception. This is passed the /// exception, its 0-based depth (where 0 is the root exception), and the outer (i.e., parent) exception.</param> public static void ForEach(Exception ex, Action <Exception, int, Exception?> action) { Conditions.RequireReference(ex, nameof(ex)); Conditions.RequireReference(action, nameof(action)); ForEach(ex, 0, null, action); }
/// <summary> /// Validates the XML using the given schema set. /// </summary> /// <param name="xml">The XML to validate.</param> /// <param name="schemas">The schemas to use for validation.</param> /// <returns>A list of validation errors and warnings.</returns> public static IList <ValidationEventArgs> Validate(this XElement xml, XmlSchemaSet schemas) { Conditions.RequireReference(xml, nameof(xml)); Conditions.RequireReference(schemas, "schema"); List <ValidationEventArgs> result = new(); XmlReaderSettings settings = new() { Schemas = schemas, ValidationType = ValidationType.Schema, }; settings.ValidationEventHandler += (s, e) => result.Add(e); using (XmlReader xmlReader = xml.CreateReader()) using (XmlReader validatingReader = XmlReader.Create(xmlReader, settings)) { while (validatingReader.Read()) { // We have to read through every node to complete validation. } } return(result); }
/// <summary> /// Adds a <paramref name="source"/> collection into a <paramref name="target"/> collection. /// </summary> /// <typeparam name="T">The type of item to add.</typeparam> /// <param name="target">The collection to add the items to.</param> /// <param name="source">The collection to get the items from.</param> /// <remarks> /// The adding behavior depends on the <paramref name="target"/> collection's <see cref="ICollection{T}.Add"/> /// implemenation. For example, if <paramref name="target"/> is a generic List, then this could add duplicates. /// However, if <paramref name="target"/> is a generic HashSet, then this won't add duplicates. /// </remarks> public static void AddRange <T>(this ICollection <T> target, IEnumerable <T> source) { Conditions.RequireReference(target, nameof(target)); Conditions.RequireReference(source, nameof(source)); foreach (T item in source) { target.Add(item); } }
/// <summary> /// Gets a read-only set. /// </summary> /// <param name="value">The set to wrap or return. This must be non-null.</param> /// <returns>If the original <paramref name="value"/> is already read-only, this method /// returns it as is. If it is not read-only, this method returns a read-only wrapper around it.</returns> public static ISet <T> AsReadOnly <T>(this ISet <T> value) { Conditions.RequireReference(value, nameof(value)); ISet <T> result = value; if (result != null && !result.IsReadOnly) { result = new ReadOnlySet <T>(value); } return(result !); }
/// <summary> /// Gets whether the assembly was built with a debug configuration. /// </summary> /// <param name="assembly">The assembly to check.</param> /// <returns>True if the <see cref="AssemblyConfigurationAttribute"/> is present /// and the configuration string contains "Debug". False otherwise.</returns> public static bool IsDebugBuild(Assembly assembly) { Conditions.RequireReference(assembly, nameof(assembly)); bool result = false; if (assembly != null) { var configuration = (AssemblyConfigurationAttribute?)assembly.GetCustomAttribute(typeof(AssemblyConfigurationAttribute)); result = configuration?.Configuration?.Contains("Debug") ?? false; } return(result); }
/// <summary> /// Gets a read-only dictionary. /// </summary> /// <param name="dictionary">The dictionary to wrap or return. This must be non-null.</param> /// <returns>If the original <paramref name="dictionary"/> is already read-only, this method /// returns it as is. If it is not read-only, this method returns a read-only wrapper around it.</returns> public static IDictionary <TKey, TValue> AsReadOnly <TKey, TValue>(this IDictionary <TKey, TValue> dictionary) where TKey : notnull { Conditions.RequireReference(dictionary, nameof(dictionary)); IDictionary <TKey, TValue> result = dictionary; if (result != null && !result.IsReadOnly) { result = new ReadOnlyDictionary <TKey, TValue>(dictionary); } return(result !); }
/// <summary> /// Gets the assembly's copyright information. /// </summary> /// <param name="assembly">The assembly to get the copyright from.</param> /// <returns>User-friendly copyright information.</returns> public static string?GetCopyright(Assembly assembly) { Conditions.RequireReference(assembly, nameof(assembly)); string?result = null; var copyrights = (AssemblyCopyrightAttribute[])assembly.GetCustomAttributes(typeof(AssemblyCopyrightAttribute), true); if (copyrights.Length > 0) { result = copyrights[0].Copyright; } return(result); }
/// <summary> /// Tokenize the input string using the specified separator (e.g., comma), delimiter (e.g., double quote), /// and trimming options and add the output tokens to the specified collection. /// </summary> /// <param name="text">The text to tokenize.</param> /// <param name="separator">The token separator character. Typically, a comma.</param> /// <param name="delimiter">The token quote character. Typically, a double quote. /// This can be used to enclose tokens that contain the separator character. /// To use this character inside a token, use it two consecutive times. /// </param> /// <param name="addToken">The action to invoke when a token has been matched and needs to be output.</param> /// <returns>True if all of the tokens were properly delimited (or were not delimited). /// False if the final token was open-delimited but never closed (which usually indicates a partial record).</returns> public static bool SplitIntoTokens(string text, char separator, char?delimiter, Action <string> addToken) { Conditions.RequireReference(addToken, nameof(addToken)); bool result; if (delimiter == null) { SplitIntoTokensWithoutDelimiter(text, separator, addToken); result = true; } else { result = SplitIntoTokensWithDelimiter(text, separator, delimiter.Value, addToken); } return(result); }
/// <summary> /// Gets the specified attribute's value or a default value if the attribute isn't present. /// </summary> /// <param name="element">The element to get the attribute from.</param> /// <param name="name">The name of the attribute to read.</param> /// <param name="defaultValue">The value to return if the attribute isn't found.</param> /// <param name="useDefaultIfEmptyValue">If the attribute is present but with an empty value, /// then this parameter determines whether the <paramref name="defaultValue"/> should be /// returned (if true) or the actual, empty attribute value should be returned (if false). /// Normally, this should be true, but false is useful if you need to allow the user to explicitly /// set an empty attribute value to override a non-empty default value. /// </param> /// <returns>The value of the attribute, or the default value if the attribute isn't found.</returns> public static string?GetAttributeValueN(this XElement element, XName name, string?defaultValue, bool useDefaultIfEmptyValue) { // It's ok if defaultValue is null. Conditions.RequireReference(element, nameof(element)); Conditions.RequireReference(name, nameof(name)); string?result = defaultValue; XAttribute?attr = element.Attribute(name); if (attr != null) { result = attr.Value; if (useDefaultIfEmptyValue && string.IsNullOrEmpty(result)) { result = defaultValue; } } return(result); }
/// <summary> /// Gets the UTC build timestamp from the assembly. /// </summary> /// <param name="assembly">The assembly to get the BuildTime metadata from.</param> /// <returns>The assembly's build time as a UTC datetime if an <see cref="AssemblyMetadataAttribute"/> is found /// with Key="BuildTime" and Value equal to a parsable UTC datetime. Returns null otherwise.</returns> public static DateTime?GetBuildTime(Assembly assembly) { Conditions.RequireReference(assembly, nameof(assembly)); const DateTimeStyles UseUtc = DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal; DateTime? result = assembly.GetCustomAttributes <AssemblyMetadataAttribute>() .Where(metadata => string.Equals(metadata.Key, "BuildTime")) .Select(metadata => DateTime.TryParse(metadata.Value, null, UseUtc, out DateTime value) ? value : (DateTime?)null) .FirstOrDefault(value => value != null); if (result != null) { result = result.Value.Kind switch { DateTimeKind.Unspecified => DateTime.SpecifyKind(result.Value, DateTimeKind.Utc), DateTimeKind.Local => result.Value.ToUniversalTime(), _ => result.Value, }; } return(result); }
/// <summary> /// Strips the opening and closing quotes off of a string if they exist. /// </summary> /// <param name="text">The text to search.</param> /// <param name="openQuote">The opening quote string.</param> /// <param name="closeQuote">The closing quote string.</param> /// <returns>The text with the quotes removed.</returns> public static string StripQuotes(string text, string openQuote, string closeQuote) { Conditions.RequireReference(text, nameof(text)); // An empty string is ok. Conditions.RequireString(openQuote, nameof(openQuote)); Conditions.RequireString(closeQuote, nameof(closeQuote)); int startIndex = 0; int length = text.Length; if (text.StartsWith(openQuote)) { int quoteLength = openQuote.Length; startIndex += quoteLength; length -= quoteLength; } if (text.EndsWith(closeQuote)) { length -= closeQuote.Length; } return(text.Substring(startIndex, length)); }
/// <summary> /// Ensures that the given text has the specified quotes at the start and end. /// </summary> /// <param name="text">The text to quote if necessary.</param> /// <param name="openQuote">The opening quote mark to use.</param> /// <param name="closeQuote">The closing quote mark to use.</param> /// <returns>The text enclosed in quotes.</returns> public static string EnsureQuotes(string text, string openQuote, string closeQuote) { Conditions.RequireReference(text, nameof(text)); // An empty string is ok. Conditions.RequireString(openQuote, nameof(openQuote)); Conditions.RequireString(closeQuote, nameof(closeQuote)); bool needsOpenQuote = !text.StartsWith(openQuote); bool needsCloseQuote = !text.EndsWith(closeQuote); if (needsOpenQuote && needsCloseQuote) { text = string.Concat(openQuote, text, closeQuote); } else if (needsOpenQuote) { text = openQuote + text; } else if (needsCloseQuote) { text += closeQuote; } return(text); }
/// <summary> /// Creates a new instance. /// </summary> /// <remarks> /// See the <see cref="Disposer"/> class comments for an example of using an anonymous /// dispose method. /// </remarks> /// <param name="disposeMethod">The method to invoke during disposal.</param> public Disposer(Action disposeMethod) { Conditions.RequireReference(disposeMethod, nameof(disposeMethod)); this.disposeMethod = disposeMethod; }
/// <summary> /// Creates a new instance that wraps the supplied <paramref name="source"/>. /// </summary> /// <param name="source">The set to wrap.</param> public ReadOnlySet(ISet <T> source) { Conditions.RequireReference(source, nameof(source)); this.source = source; }
/// <summary> /// Creates a new instance for the specified settings node. /// </summary> /// <param name="settings">A settings node.</param> public SettingsEventArgs(ISettingsNode settings) { Conditions.RequireReference(settings, nameof(settings)); this.settings = settings; }
/// <summary> /// Creates a new XmlSchemaSet from the XElement containing the schema and stores /// any errors and warnings in the specified collection. /// </summary> /// <param name="schemaElements">The XML elements containing the schemas.</param> /// <param name="errors">The collection to add errors and warnings into.</param> /// <returns>The new XmlSchemaSet instance.</returns> public static XmlSchemaSet CreateSchemaSet(IEnumerable <XElement> schemaElements, ICollection <ValidationEventArgs> errors) { Conditions.RequireReference(errors, nameof(errors)); return(CreateSchemaSet(schemaElements, (s, e) => errors.Add(e))); }