Skip to content

Instantly share code, notes, and snippets.

@JKamsker
Last active August 1, 2024 08:36
Show Gist options
  • Save JKamsker/ab3964f647e4bcb02e6dbd617f08acb0 to your computer and use it in GitHub Desktop.
Save JKamsker/ab3964f647e4bcb02e6dbd617f08acb0 to your computer and use it in GitHub Desktop.
A collection of snippets i need every now and then and not sure if i want to create a library for atm
public record GenericArgumentResult(Type RequestedType, Type DirectSubclass, Type[] GenericArguments);
public static class GenericTypeHelper
{
/// <summary>
/// Returns <see cref="TEntity"/> of <see cref="EntityTypeBuilder{TEntity}"/>,
/// even from <see cref="EntityTypeBuilderOfEntity"/> : <see cref="EntityTypeBuilder{TEntity}"/>.
/// </summary>
/// <param name="concreteType">For e.g EntityTypeBuilderOfEntity.</param>
/// <param name="genericDefinition">for e.g typeof(<see cref="EntityTypeBuilder{}"/>).</param>
/// <returns>An enumerable of generic arguments of <paramref name="genericDefinition"/>.</returns>
public static IEnumerable<GenericArgumentResult> GetGenericArgumentsOfTypeDefinition(
Type concreteType,
Type genericDefinition
)
{
return EnumerateBaseTypesAndInterfaces(concreteType)
.Where(x => x.IsGenericType)
.Where(x => x.GetGenericTypeDefinition() == genericDefinition)
.Select(x => new GenericArgumentResult(
RequestedType: concreteType,
DirectSubclass: x,
GenericArguments: x.GenericTypeArguments
));
}
// same as GetGenericArgumentsOfTypeDefinition but returns only the first result
public static GenericArgumentResult? GetFirstGenericArgumentsOfTypeDefinition(
Type concreteType,
Type genericDefinition,
Func<GenericArgumentResult, bool>? predicate = null
)
{
var result = GetGenericArgumentsOfTypeDefinition(concreteType, genericDefinition);
if (predicate != null)
{
result = result.Where(predicate);
}
return result.FirstOrDefault();
}
private static IEnumerable<Type> EnumerateBaseTypesAndInterfaces(Type? type, bool returnInput = true)
{
if (type == null)
{
yield break;
}
if (returnInput)
{
yield return type;
}
// Return all base types
var current = type.BaseType;
while (current != null)
{
foreach (var interfaceType in current.GetInterfaces())
{
yield return interfaceType;
}
yield return current;
current = current.BaseType;
}
// Return all interfaces
foreach (var interfaceType in type.GetInterfaces())
{
yield return interfaceType;
}
}
}
namespace Tests.UtilitiyTests;
public class GenericTypeHelperTests
{
[Theory]
[InlineData(typeof(Provider1), typeof(Config1))]
[InlineData(typeof(Provider2), typeof(Config2))]
[InlineData(typeof(Provider3), typeof(Config3))]
[InlineData(typeof(Provider3_1), typeof(Config3))]
public void TestSimpleGenericArgument(Type concreteType, params Type[] arguments)
{
var container = GenericTypeHelper
.GetGenericArgumentsOfTypeDefinition(concreteType, typeof(Provider<>))
.Where(x => x.GenericArguments.Length == arguments.Length)
.FirstOrDefault()
;
Assert.NotNull(container);
var args = container.GenericArguments;
Assert.Equal(arguments.Length, args.Length);
Assert.True(arguments.SequenceEqual(args));
}
[Theory]
[InlineData(typeof(Provider_06), typeof(Config1), typeof(Config2))]
public void TestScatteredGenericArguments(Type concreteType, params Type[] arguments)
{
var containers = GenericTypeHelper
.GetGenericArgumentsOfTypeDefinition(concreteType, typeof(Provider_04<,>))
.Where(x => x.GenericArguments.Length == arguments.Length)
.ToArray();
var container = containers.FirstOrDefault();
Assert.NotNull(container);
var args = container.GenericArguments;
Assert.Equal(arguments.Length, args.Length);
Assert.True(arguments.SequenceEqual(args));
}
[Theory]
[InlineData(typeof(Provider_06), typeof(Config1), typeof(Config2))]
public void TestScatteredGenericArguments_Interface(Type concreteType, params Type[] arguments)
{
var containers = GenericTypeHelper
.GetGenericArgumentsOfTypeDefinition(concreteType, typeof(IProvider_04<,>))
.Where(x => x.GenericArguments.Length == arguments.Length)
.ToArray();
var container = containers.FirstOrDefault();
Assert.NotNull(container);
var args = container.GenericArguments;
Assert.Equal(arguments.Length, args.Length);
Assert.True(arguments.SequenceEqual(args));
}
public interface IProvider_04<TConfig, TConfig1>
{
}
public class Provider_04<TConfig, TConfig1> : IProvider_04<TConfig, TConfig1>
{
}
public class Provider_05<TConfig> : Provider_04<Config1, TConfig>
{
}
public class Provider_06 : Provider_05<Config2>
{
}
public abstract class ErpSyncProvider
{
}
public abstract class Provider<TConfig> : ErpSyncProvider
where TConfig : ConfigItem
{
}
public class ConfigItem
{
}
public class Provider1 : Provider<Config1>
{
}
public class Provider2 : Provider<Config2>
{
}
public class Provider3 : Provider<Config3>
{
}
public class Provider3_1 : Provider3
{
}
public class Config1 : ConfigItem
{
}
public class Config2 : ConfigItem
{
}
public class Config3 : ConfigItem
{
}
}
public class TempFile : IDisposable
{
public string Location { get; }
public bool Exists => System.IO.File.Exists(Location);
public TempFile(string path)
{
Location = path;
}
/// <summary>
/// Creates a TempFile with a random name in the temp directory.
/// </summary>
/// <returns></returns>
public static TempFile CreateRandom()
{
return CreateWithExtension(".tmp");
}
/// <summary>
/// Finds a random filename that does not exist in the temp directory and creates a TempFile with that name.
/// Does not actually create the file on disk.
/// </summary>
/// <param name="extension"></param>
/// <returns></returns>
public static TempFile CreateWithExtension(string extension)
{
var tempDirectory = System.IO.Path.GetTempPath();
while (true)
{
var randomName = Random.Shared.NextString(10);
var sb = new StringBuilder();
sb.Append(tempDirectory);
sb.Append(randomName);
if (extension.StartsWith("."))
{
sb.Append(extension);
}
else
{
sb.Append('.');
sb.Append(extension);
}
var newPath = sb.ToString();
if (!System.IO.File.Exists(newPath))
{
return new TempFile(newPath);
}
}
}
public async Task WriteAllTextAsync(string content)
{
using var stream = System.IO.File.OpenWrite(Location);
using var writer = new StreamWriter(stream);
await writer.WriteAsync(content);
}
// ReadAllTextAsync
public async Task<string> ReadAllTextAsync()
{
using var stream = System.IO.File.OpenRead(Location);
using var reader = new StreamReader(stream);
return await reader.ReadToEndAsync();
}
public async Task WriteAllBytesAsync(byte[] bytes)
{
await System.IO.File.WriteAllBytesAsync(Location, bytes);
}
public void OverwriteFrom(FileInfo sourcePath)
{
System.IO.File.Copy(sourcePath.FullName, Location, true);
}
public void OverwriteFrom(string sourcePath)
{
System.IO.File.Copy(sourcePath, Location, true);
}
public void OverwriteFrom(Stream stream)
{
using var fs = new FileStream(Location, FileMode.Create, FileAccess.Write);
fs.Position = 0;
stream.CopyTo(fs);
if (fs.Position < fs.Length)
{
fs.SetLength(fs.Position);
}
}
public Stream OpenWrite()
{
FileEx.DeleteIfExists(Location);
return System.IO.File.OpenWrite(Location);
}
public void Dispose()
{
FileEx.DeleteIfExists(Location);
}
}
public class TempDirectory : IDisposable
{
public string Path { get; }
public bool Exists => Directory.Exists(Path);
public TempDirectory(string path)
{
Path = path;
}
// alias CreateTempDirectory
public static TempDirectory CreateTempDirectory()
=> CreateRandom();
public static TempDirectory CreateRandom()
{
var tempDirectory = System.IO.Path.GetTempPath();
while (true)
{
var randomName = Random.Shared.NextString(10);
var sb = new StringBuilder();
sb.Append(tempDirectory);
sb.Append(randomName);
var newPath = sb.ToString();
if (!Directory.Exists(newPath))
{
Directory.CreateDirectory(newPath);
return new TempDirectory(newPath);
}
}
}
public void Dispose()
{
Directory.Delete(Path, true);
}
public FileInfo GetFile(string name)
{
var path = System.IO.Path.Combine(Path, name);
return new FileInfo(path);
}
public DirectoryInfo GetDirectory(string name)
{
var path = System.IO.Path.Combine(Path, name);
return new DirectoryInfo(path);
}
public FileInfo CreateFileWithExtension(string extension)
{
var path = System.IO.Path.Combine(Path, Random.Shared.NextString(10) + extension);
File.Create(path).Dispose();
return new FileInfo(path);
}
// CreateDirectory
public DirectoryInfo CreateSubDirectory(string name)
{
var path = System.IO.Path.Combine(Path, name);
return Directory.CreateDirectory(path);
}
}
public static class FileEx
{
public static void DeleteIfExists(string filename)
{
if (System.IO.File.Exists(filename))
{
System.IO.File.Delete(filename);
}
}
public static FileStream OpenReadAsync(string filename)
{
return new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true);
}
}
public static class RandomExtensions
{
private const string DefaultCharset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
// NextMilliseconds
public static TimeSpan NextMilliseconds(this Random random, int min, int max)
{
return TimeSpan.FromMilliseconds(random.Next(min, max));
}
public static TimeSpan NextTimeSpan(this Random random, TimeSpan min, TimeSpan max)
{
return random.NextMilliseconds((int)min.TotalMilliseconds, (int)max.TotalMilliseconds);
}
public static string NextString(this Random random, int length, string charset = DefaultCharset)
{
var sb = new StringBuilder(length);
for (int i = 0; i < length; i++)
{
sb.Append(charset[random.Next(charset.Length)]);
}
return sb.ToString();
}
}
public class WaitTimeGenerator
{
public int MaxRetries { get; }
public TimeSpan BaseTime { get; }
public TimeSpan MaxWaitTime { get; }
public bool ReturnNullOnMaxRetries { get; }
/// <summary>
/// Initializes a new instance of the <see cref="WaitTimeGenerator"/> class with specified settings for retry logic.
/// </summary>
/// <param name="maxRetries">The maximum number of retry attempts. Specifies how many times a retry should be attempted before giving up. Must be a non-negative integer.</param>
/// <param name="baseTime">The base wait time before the first retry attempt. This serves as the initial delay and influences the exponential backoff calculation for subsequent retries. The wait time grows exponentially based on this value but will not exceed <paramref name="maxWaitTime"/>.</param>
/// <param name="maxWaitTime">The maximum wait time between retries. This value caps the wait time, ensuring that the delay between retry attempts does not become excessively long, regardless of the number of attempts.</param>
/// <param name="returnNullOnMaxRetries">Determines the behavior once the maximum number of retries (<paramref name="maxRetries"/>) is reached. If set to <c>true</c>, <see cref="GetWaitTime"/> will return <c>null</c> for any retry attempt beyond the maximum, indicating that no further retries should be attempted. If <c>false</c>, it will continue to return the <paramref name="maxWaitTime"/> for any count beyond <paramref name="maxRetries"/>, potentially allowing retries to continue indefinitely at the maximum interval. Defaults to <c>true</c>.</param>
/// <remarks>
/// The <see cref="WaitTimeGenerator"/> class provides a flexible way to manage retry intervals in applications, using an exponential backoff strategy. It is designed to help handle transient failures by intelligently spacing out retry attempts, with the ability to configure the initial delay, the growth rate of the delay, and the maximum delay, as well as the overall behavior after the maximum number of retries is exceeded.
/// </remarks>
public WaitTimeGenerator(int maxRetries, TimeSpan baseTime, TimeSpan maxWaitTime, bool returnNullOnMaxRetries = true)
{
MaxRetries = maxRetries;
BaseTime = baseTime;
MaxWaitTime = maxWaitTime;
ReturnNullOnMaxRetries = returnNullOnMaxRetries;
}
public TimeSpan? GetWaitTime(int retryCount, int? maxRetryOverride = null)
{
var maxRetries = maxRetryOverride ?? MaxRetries;
if (ReturnNullOnMaxRetries && retryCount > maxRetries)
{
return null;
}
double factor = Math.Pow(MaxWaitTime.TotalSeconds / BaseTime.TotalSeconds, 1.0 / maxRetries);
double waitTimeInSeconds = BaseTime.TotalSeconds * Math.Pow(factor, retryCount);
waitTimeInSeconds = Math.Min(waitTimeInSeconds, MaxWaitTime.TotalSeconds);
return TimeSpan.FromSeconds(waitTimeInSeconds);
}
public IEnumerable<TimeSpan?> GetWaitTimes()
{
for (int i = 0;; i++)
{
var time = GetWaitTime(i);
if (time == null)
{
yield break;
}
yield return time;
}
}
public static WaitTimeGeneratorBuilder Create => new WaitTimeGeneratorBuilder();
}
public class WaitTimeGeneratorBuilder
{
private int? _maxRetries;
private TimeSpan? _baseTime;
private TimeSpan? _maxWaitTime;
private bool? _returnNullOnMaxRetries;
public static WaitTimeGeneratorBuilder Create => new WaitTimeGeneratorBuilder();
public WaitTimeGeneratorBuilder WithMaxRetries(int maxRetries)
{
_maxRetries = maxRetries;
_returnNullOnMaxRetries = true;
return this;
}
/// <summary>
/// Alias of <see cref="WithBaseTime"/>.
/// </summary>
public WaitTimeGeneratorBuilder WithMinWaitTime(TimeSpan baseTime)
=> WithBaseTime(baseTime);
public WaitTimeGeneratorBuilder WithBaseTime(TimeSpan baseTime)
{
_baseTime = baseTime;
return this;
}
public WaitTimeGeneratorBuilder WithMaxWaitTime(TimeSpan maxWaitTime)
{
_maxWaitTime = maxWaitTime;
return this;
}
public WaitTimeGeneratorBuilder MaxWaitTimeAfter(int maxRetries)
{
_maxRetries = maxRetries;
_returnNullOnMaxRetries = false;
return this;
}
public WaitTimeGenerator Build()
{
if (_maxRetries == null)
{
throw new InvalidOperationException("MaxRetries must be set.");
}
if (_baseTime == null)
{
throw new InvalidOperationException("BaseTime must be set.");
}
if (_maxWaitTime == null)
{
throw new InvalidOperationException("MaxWaitTime must be set.");
}
return new WaitTimeGenerator(_maxRetries.Value, _baseTime.Value, _maxWaitTime.Value, _returnNullOnMaxRetries ?? false);
}
}
using System.Collections.Concurrent;
public class WeakDictionary<TKey, TValue>
where TKey : class
where TValue : class
{
private readonly ConcurrentDictionary<TKey, WeakReference<WeakEntry>> _dictionary = new();
public bool TryGetValue(TKey key, out TValue value)
{
if (_dictionary.TryGetValue(key, out var weakEntry))
{
if (weakEntry?.TryGetTarget(out var entry) == true)
{
value = entry.Value;
return true;
}
else
{
_dictionary.TryRemove(key, out _);
}
}
value = default;
return false;
}
public void Add(TKey key, TValue value)
{
_dictionary.AddOrUpdate
(
key: key,
addValue: new WeakReference<WeakEntry>
(
new WeakEntry(key, value, (k, v) => _dictionary.TryRemove(k, out _))
),
updateValueFactory: (k, v) =>
{
v.SetTarget(new WeakEntry(key, value, (k, v) =>
{
_dictionary.Remove(k, out _);
}));
return v;
}
);
}
private class WeakEntry : IDisposable
{
private readonly Action<TKey, TValue> _disposeAction;
public TKey Key { get; private set; }
public TValue Value { get; private set; }
public bool IsDisposed { get; private set; }
public WeakEntry(TKey key, TValue value, Action<TKey, TValue> disposeAction)
{
Key = key;
Value = value;
_disposeAction = disposeAction;
}
// disposer and finalizer are used to clean up the dictionary when the value is no longer referenced
public void Dispose()
{
if (IsDisposed)
{
return;
}
IsDisposed = true;
_disposeAction(Key, Value);
Key = null;
Value = null;
}
~WeakEntry()
{
Dispose();
}
// equality is based on the key and value
public override bool Equals(object obj)
{
if (obj is WeakEntry other)
{
return Key.Equals(other.Key) && Value.Equals(other.Value);
}
return false;
}
public override int GetHashCode()
{
return Key.GetHashCode() ^ Value.GetHashCode();
}
public static bool operator ==(WeakEntry left, WeakEntry right)
{
return left.Equals(right);
}
public static bool operator !=(WeakEntry left, WeakEntry right)
{
return !(left == right);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment