Skip to content

Instantly share code, notes, and snippets.

@neremin
Last active August 29, 2015 14:14
Show Gist options
  • Save neremin/c5b967ed0fd8bf38958f to your computer and use it in GitHub Desktop.
Save neremin/c5b967ed0fd8bf38958f to your computer and use it in GitHub Desktop.
/// <devdoc>
/// Useful in number of places that return an empty byte array to avoid unnecessary memory allocation.
/// Borrowed from .NET Framework's Array.cs.
/// </devdoc>
public static class EmptyArray<T>
{
public static readonly T[] Value = new T[0];
}
public static class StringExtensions
{
static readonly WeakLazy<char[]> InvalidFileNameChars = new WeakLazy<char[]>(Path.GetInvalidFileNameChars);
static readonly WeakLazy<char[]> InvalidPathChars = new WeakLazy<char[]>(Path.GetInvalidPathChars);
[Pure]
public static bool IsValidFileName(this string name)
{
return !string.IsNullOrEmpty(name) && name.IndexOfAny(InvalidFileNameChars.Value) == -1;
}
[Pure]
public static bool IsValidFilePath(this string name)
{
return !string.IsNullOrEmpty(name) && name.IndexOfAny(InvalidPathChars.Value) == -1;
}
public static string ReplaceInvalidFileNameChars(this string name, string replacement = null)
{
return Replace(name, replacement, InvalidFileNameChars.Value);
}
public static string ReplaceInvalidFilePathChars(this string name, string replacement = null)
{
return Replace(name, replacement, InvalidPathChars.Value);
}
public static string Replace(this string value, string replacement, params char[] replacedChars)
{
if (string.IsNullOrEmpty(value) || replacedChars == null)
{
return value;
}
var start = 0;
var length = value.Length;
var position = -1;
var result = new StringBuilder(length);
for (; start < length && (position = value.IndexOfAny(replacedChars, start)) != -1; start = position + 1)
{
result.Append(value, start, position - start);
result.Append(replacement);
}
result.Append(value, start, Math.Max(position, length) - start);
return result.ToString();
}
public static IEnumerable<T> SplitAndConvert<T>(this string value, string separator,
Converter<string, T> converter, bool skipEmpty = true)
{
return string.IsNullOrEmpty(value)
? EmptyArray<T>.Value
: value.Split
(
new[] { separator },
(skipEmpty ? StringSplitOptions.RemoveEmptyEntries : StringSplitOptions.None)
)
.Select(element => converter(element));
}
public static IEnumerable<string> Split(this string value, string separator, bool skipEmpty = true)
{
Contract.Requires<ArgumentNullException>(value != null);
if (value.Length == 0)
{
return EmptyArray<string>.Value;
}
var options = (skipEmpty ? StringSplitOptions.RemoveEmptyEntries : StringSplitOptions.None);
return value.Split(new[] {separator}, options);
}
public static IEnumerable<T> SplitAndConvert<T>(this string value, string separator,
Converter<string, T> converter, bool skipEmpty = true)
{
Contract.Requires<ArgumentNullException>(converter != null);
return value.Split(separator, skipEmpty).Select(element => converter(element));
}
public static string AppendLine(this string firstLine, string line)
{
return string.Concat(firstLine, Environment.NewLine, line);
}
public static string AppendLines(this string firstLine, IEnumerable<string> lines)
{
Contract.Requires<ArgumentNullException>(firstLine != null);
Contract.Requires<ArgumentNullException>(lines != null);
return lines.Prepend(firstLine).JoinLines(); // Requires LinqExtensions.cs
}
public static string Args(this string format, params object[] args)
{
return string.Format(format, args);
}
}
[TestFixture]
public class StringExtensionsTests
{
[TestCase(null, "9", "*", Result = null)]
[TestCase("*", null, "*", Result = "")]
[TestCase("*", "", "*", Result = "")]
[TestCase("", "9", "*", Result = "")]
[TestCase("*", "9", "*", Result = "9")]
[TestCase("*", "9", null, Result = "*")]
[TestCase("*-*-*", "99", "*", Result = "99-99-99")]
[TestCase("-*-*-*-", "99", "*", Result = "-99-99-99-")]
[TestCase("________", "9", "*", Result = "________")]
[TestCase("_*__*___**_*", "9", "*", Result = "_9__9___99_9")]
[TestCase("*__*__**_*___", "9", "*", Result = "9__9__99_9___")]
public string Replace(string input, string replacement, string replacedChars)
{
return input.Replace(replacement, replacedChars == null ? null : replacedChars.ToCharArray());
}
[TestCase("_1__23__4_5", "*", "1234567890", 1000)]
public void Replace_Performance(string input, string replacement, string replacedChars, int iterations)
{
var value = Enumerable.Repeat(input, 10000).ConcatStrings();
var chars = replacedChars.ToCharArray();
Console.WriteLine("Length: " + value.Length);
Console.WriteLine("Cardinality: " + value.Length * replacedChars.Length);
GC.Collect(3, GCCollectionMode.Forced);
var stopwatch = Stopwatch.StartNew();
for (int i = 0; i < iterations; ++i)
{
value.Replace(replacement, chars);
}
Console.WriteLine("Replace: " + stopwatch.Elapsed);
GC.Collect(3, GCCollectionMode.Forced);
stopwatch.Restart();
for (int i = 0; i < iterations; ++i)
{
string.Join(replacement, value.Split(chars, StringSplitOptions.None));
}
Console.WriteLine("Split & Join: " + stopwatch.Elapsed);
var charCodes = string.Concat(chars.Select(c => @"\u" + ((short)c).ToString("x4")));
var charsPattern = @"[" + charCodes + "]";
var regex = new Regex(charsPattern, RegexOptions.Compiled);
GC.Collect(3, GCCollectionMode.Forced);
stopwatch.Restart();
for (int i = 0; i < iterations; ++i)
{
regex.Replace(value, replacement);
}
Console.WriteLine("Regex: " + stopwatch.Elapsed);
}
[TestCase(null, ",", true, ";", ExpectedException = typeof(ArgumentNullException))]
[TestCase("1234", null, true, ";", Result = "1234")]
[TestCase("1234", "", true, ";", Result = "1234")]
[TestCase(",1,2,3,4,5,", ",", true, ";", Result = "1;2;3;4;5")]
[TestCase(",1,2,3,4,5,", ",", false, ";", Result = ";1;2;3;4;5;")]
public string Split(string value, string separator, bool skipEmpty, string join)
{
var result = value.Split(separator, skipEmpty);
return result.Join(join);
}
[TestCase(",1,2,3,4,5,", ",", "*", true, ";", Result = "*;*;*;*;*")]
[TestCase(",1,2,3,4,5,", ",", "*", false, ";", Result = "*;*;*;*;*;*;*")]
public string SplitAndConvert(string value, string separator, string replacement, bool skipEmpty, string join)
{
var result = value.SplitAndConvert(separator, s => replacement, skipEmpty);
return result == null ? null : result.Join(join);
}
}
public sealed class WeakLazy<T> where T: class
{
readonly WeakReference<T> reference;
readonly Func<T> valueFactory;
public WeakLazy(Func<T> valueFactory)
{
Contract.Requires<ArgumentNullException>(valueFactory != null);
reference = new WeakReference<T>(null);
this.valueFactory = valueFactory;
}
public T Value
{
get
{
T value;
if (!reference.TryGetTarget(out value))
{
value = valueFactory();
reference.SetTarget(value);
}
return value;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment