![]() |
VOOZH | about |
dotnet add package Rystem --version 10.0.8
NuGet\Install-Package Rystem -Version 10.0.8
<PackageReference Include="Rystem" Version="10.0.8" />
<PackageVersion Include="Rystem" Version="10.0.8" />Directory.Packages.props
<PackageReference Include="Rystem" />Project file
paket add Rystem --version 10.0.8
#r "nuget: Rystem, 10.0.8"
#:package Rystem@10.0.8
#addin nuget:?package=Rystem&version=10.0.8Install as a Cake Addin
#tool nuget:?package=Rystem&version=10.0.8Install as a Cake Tool
Rystem is the core utilities package of the Rystem ecosystem.
It extends familiar BCL namespaces such as System, System.Linq, System.Linq.Expressions, System.Reflection, System.Text, and System.Threading.Tasks with pragmatic helpers for:
Most examples below are adapted from the repository test suite so the README stays aligned with real usage.
For brevity, short snippets sometimes use obvious sample types such as User, Order, MyType, or MyDto.
dotnet add package Rystem
The current 10.x package targets net10.0.
After installation, most APIs are available through standard using directives that match the namespace they extend. The main exception is ReflectionHelper, which lives in Rystem.Reflection.
If you move deeper into the ecosystem, Rystem.DependencyInjection builds DI modules on top of this package, and Rystem.DependencyInjection.Web adds ASP.NET Core runtime rebuilding on top of the DI layer.
| Area | Main APIs |
|---|---|
| Discriminated unions | AnyOf<T0, ...>, Match, MatchAsync, Switch, SwitchAsync, TryGetTn, JSON selectors and defaults |
| Core helpers | Stopwatch, Try, Cast<T>, ToDeepCopy, CopyPropertiesFrom, enum and string helpers |
| Text and data | ToByteArray, ToStream, ReadLinesAsync, ToJson, ToHash, ToBase64, ToBase45, ToCsv, ToMinimize |
| LINQ and expressions | Serialize, Deserialize, DeserializeAsDynamic, ChangeReturnType, InvokeAsync, Where(LambdaExpression), Select(LambdaExpression), CallMethodAsync |
| Reflection and runtime | FetchProperties, CreateWithDefault, CreateInstance, ConstructWithBestDynamicFit, ToShowcase, GetBodyAsString, Model.Create(...) |
| Tasks and collections | NoContext, ToResult, ToListAsync, TaskManager, ConcurrentList<T>, AsyncEnumerable<T>.Empty |
| Conversion | ConvertAs(ProgrammingLanguageType.Typescript) |
If you want a single package with a broad set of low-level .NET utilities, this is the entry point of the Rystem ecosystem.
AnyOf<T0, T1, ...> brings discriminated unions to C# with built-in System.Text.Json integration.
The package ships AnyOf variants from 2 to 10 generic arguments.
AnyOf<int, string, bool> value = "hello";
Console.WriteLine(value.Index); // 1
Console.WriteLine(value.IsT1); // true
Console.WriteLine(value.AsT1); // hello
value = 42;
int number = value.CastT0;
Useful members include:
IndexIsT0, IsT1, ...AsT0, AsT1, ...CastT0, CastT1, ...Is<T>()TryGetT0(out value), TryGetT1(out value), ...AnyOf<int, string, bool> value = "hello";
string description = value.Match(
number => $"int: {number}",
text => $"string: {text}",
flag => $"bool: {flag}")!;
value.Switch(
number => Console.WriteLine(number),
text => Console.WriteLine(text),
flag => Console.WriteLine(flag));
string? asyncDescription = await value.MatchAsync(
async number => await Task.FromResult($"int: {number}"),
async text => await Task.FromResult($"string: {text}"),
async flag => await Task.FromResult($"bool: {flag}"));
await value.SwitchAsync(
number => { Console.WriteLine(number); return ValueTask.CompletedTask; },
text => { Console.WriteLine(text); return ValueTask.CompletedTask; },
flag => { Console.WriteLine(flag); return ValueTask.CompletedTask; });
if (value.TryGetT1(out var textValue))
Console.WriteLine(textValue);
Notes:
MatchAsync delegates return Task<TResult?>SwitchAsync delegates return ValueTaskTryGetTn is the safest way to branch without exceptionsThe active value is serialized directly, and deserialization automatically chooses the correct target type.
public sealed class Wrapper
{
public AnyOf<FirstClass, string>? Data { get; set; }
}
public sealed class FirstClass
{
public string? FirstProperty { get; set; }
public string? SecondProperty { get; set; }
}
var wrapper = new Wrapper
{
Data = new FirstClass
{
FirstProperty = "alpha",
SecondProperty = "beta"
}
};
string json = wrapper.ToJson();
Wrapper copy = json.FromJson<Wrapper>();
Console.WriteLine(copy.Data!.AsT0!.FirstProperty); // alpha
By default, union deserialization uses the JSON payload signature, meaning the set of available property names is used to find the best candidate.
public sealed class SignatureTestClass
{
public AnyOf<SignatureClassOne, SignatureClassTwo>? Test { get; set; }
}
public sealed class SignatureClassOne
{
public string? FirstProperty { get; set; }
public string? SecondProperty { get; set; }
}
public sealed class SignatureClassTwo
{
public string? FirstProperty { get; set; }
public string? SecondProperty { get; set; }
}
var payload = new SignatureTestClass
{
Test = new SignatureClassTwo
{
FirstProperty = "FirstProperty",
SecondProperty = "SecondProperty"
}
};
var copy = payload.ToJson().FromJson<SignatureTestClass>();
// Both candidate types have the same signature,
// so the first matching type wins.
Console.WriteLine(copy.Test!.Is<SignatureClassOne>()); // true
If no candidate can be matched, the union property is deserialized as null.
When multiple candidate types share the same shape, add selectors to tell the deserializer how to choose.
AnyOfJsonSelector works with both strings and non-string values.
public sealed class ChosenClass
{
public AnyOf<TheFirstChoice, TheSecondChoice>? Value { get; set; }
}
public sealed class TheFirstChoice
{
[AnyOfJsonSelector("first")]
public string Type { get; init; } = null!;
[AnyOfJsonSelector(2, 3, 4)]
public int Flexy { get; set; }
}
public sealed class TheSecondChoice
{
[AnyOfJsonSelector("first", "second")]
public string Type { get; init; } = null!;
[AnyOfJsonSelector(1)]
public int Flexy { get; set; }
}
var payload = new ChosenClass
{
Value = new TheSecondChoice
{
Type = "first",
Flexy = 1
}
};
var copy = payload.ToJson().FromJson<ChosenClass>();
Console.WriteLine(copy.Value!.Is<TheSecondChoice>()); // true
payload = new ChosenClass
{
Value = new TheSecondChoice
{
Type = "first",
Flexy = 2
}
};
copy = payload.ToJson().FromJson<ChosenClass>();
Console.WriteLine(copy.Value!.Is<TheFirstChoice>()); // true
In the second case, the raw object was originally TheSecondChoice, but the selector values identify TheFirstChoice as the correct deserialization target.
You can apply selectors to a whole class instead of a single property.
[AnyOfJsonClassSelector(nameof(FirstProperty), "first.F")]
public sealed class FirstGetClass
{
public string? FirstProperty { get; set; }
public string? SecondProperty { get; set; }
}
[AnyOfJsonRegexClassSelector(nameof(FirstProperty), "secon[^.]*.[^.]*")]
public sealed class SecondGetClass
{
public string? FirstProperty { get; set; }
public string? SecondProperty { get; set; }
}
public sealed class FourthGetClass
{
[AnyOfJsonSelector("fourth.F")]
public string? FirstProperty { get; set; }
public string? SecondProperty { get; set; }
}
public sealed class FifthGetClass
{
[AnyOfJsonRegexSelector("fift[^.]*.")]
public string? FirstProperty { get; set; }
public string? SecondProperty { get; set; }
}
[AnyOfJsonDefault]
public sealed class SixthGetClass
{
public string? FirstProperty { get; set; }
public string? SecondProperty { get; set; }
}
This lets you combine:
Measure actions, tasks, or task-returning functions.
var started = Stopwatch.Start();
await Task.Delay(2000);
var result = started.Stop();
Console.WriteLine(result.Span.TotalMilliseconds);
var result = await Stopwatch.MonitorAsync(async () =>
{
await Task.Delay(2000);
});
Console.WriteLine(result.Span.TotalMilliseconds);
var result = await Stopwatch.MonitorAsync(async () =>
{
await Task.Delay(2000);
return 3;
});
Console.WriteLine(result.Result); // 3
Console.WriteLine(result.Stopwatch.Span); // elapsed time
There is also a synchronous overload:
var result = Stopwatch.Monitor(() => DoWork());
Try wraps synchronous, Task, and ValueTask execution and returns the value plus any exception.
var success = Try.WithDefaultOnCatch(() => 42);
int value = success;
Console.WriteLine(value); // 42
Console.WriteLine(success.Exception == null); // true
var failed = Try.WithDefaultOnCatch(() => int.Parse("abc"));
Console.WriteLine((int)failed); // 0
Console.WriteLine(failed.Exception != null); // true
var asyncFailed = await Try.WithDefaultOnCatchAsync(async () =>
{
await Task.Delay(10);
return int.Parse("abc");
});
Console.WriteLine(asyncFailed.Exception != null); // true
var valueTaskResult = await Try.WithDefaultOnCatchValueTaskAsync(async () =>
{
await Task.Delay(10);
return 12;
});
You can also configure retry behavior.
var response = await Try.WithDefaultOnCatchAsync(
async () => await FlakyServiceAsync(),
behavior =>
{
behavior.MaxRetry = 3;
behavior.WaitBetweenRetry = 200;
behavior.RetryUntil = exception => exception is HttpRequestException;
});
Prefer checking response.Exception to detect failures. That is the most explicit and reliable success signal.
Use Cast<T>() for safe conversions across numeric values, strings, runtime types, and inheritance hierarchies.
int x = 2;
decimal result = x.Cast<decimal>();
int? nullable = null;
decimal result2 = nullable.Cast<decimal>(); // 0
decimal? result3 = nullable.Cast<decimal?>(); // null
string guid = Guid.NewGuid().ToString();
Guid parsed = guid.Cast<Guid>();
object entity = new User();
Type targetType = typeof(UserDto);
object converted = entity.Cast(targetType)!;
Use ToDeepCopy() to create a detached clone, or CopyPropertiesFrom(...) to copy property values onto an existing instance.
var original = new User { Id = 3 };
var copy = original.ToDeepCopy();
Console.WriteLine(ReferenceEquals(original, copy)); // false
Console.WriteLine(copy.Id); // 3
var source = new User { Id = 10 };
var target = new User();
target.CopyPropertiesFrom(source);
Console.WriteLine(target.Id); // 10
Convert strings or other enum values into a target enum and read [Display] names.
enum Color
{
Red,
Green,
Blue
}
Color c1 = "green".ToEnum<Color>();
Color c2 = ConsoleColor.Green.ToEnum<Color>();
public enum Status
{
[Display(Name = "In Progress")]
Working
}
string label = Status.Working.GetDisplayName(); // In Progress
Convert between strings, byte arrays, and streams with minimal ceremony.
string text = "daskemnlandxioasndslam dasmdpoasmdnasndaslkdmlasmv asmdsa";
byte[] bytes = text.ToByteArray();
string restored = bytes.ConvertToString();
string text = "daskemnlandxioasndslam dasmdpoasmdnasndaslkdmlasmv asmdsa";
Stream stream = text.ToStream();
string restored = stream.ConvertToString();
ReadLinesAsync() turns a stream into IAsyncEnumerable<string>.
string text = "line 1\nline 2\nline 3";
Stream stream = text.ToStream();
await foreach (var line in stream.ReadLinesAsync())
{
Console.WriteLine(line);
}
Other small helpers:
string title = "dasda".ToUpperCaseFirst(); // Dasda
bool hasTwoAs = "abcderfa".ContainsAtLeast(2, 'a');
string replaced = "aaa".Replace("a", "b", 2); // bba
string encoded = "Hello World".ToBase64();
string decoded = encoded.FromBase64();
var payload = new User { Id = 7, Name = "Ada" };
string encodedObject = payload.ToBase64();
User decodedObject = encodedObject.FromBase64<User>();
Base45 is handy when you want a compact, QR-code-friendly character set.
string encoded = "Hello World".ToBase45();
string decoded = encoded.FromBase45();
var payload = new User { Id = 7, Name = "Ada" };
string encodedObject = payload.ToBase45();
User decodedObject = encodedObject.FromBase45<User>();
Both object overloads serialize through JSON first.
ToHash() creates a deterministic SHA-512 hexadecimal hash from a string or any serializable object.
string hash = "my secret".ToHash();
var foo = new Foo
{
Values = new[] { "aa", "bb", "cc" },
X = true
};
string hash2 = foo.ToHash();
Console.WriteLine(foo.ToHash() == hash2); // true
Guid id = Guid.Parse("41e2c840-8ba1-4c0b-8a9b-781747a5de0c");
string hash = id.ToHash();
The JSON helpers are intentionally tiny wrappers over System.Text.Json.
var users = new List<User>
{
new User { Id = 1, Name = "Ada" },
new User { Id = 2, Name = "Grace" }
};
string json = users.ToJson();
List<User> copy = json.FromJson<List<User>>();
object? dynamicCopy = json.FromJson(typeof(List<User>));
await using var stream = json.ToStream();
List<User> fromStream = await stream.FromJsonAsync<List<User>>();
ToCsv() flattens objects, nested objects, and enumerable members into a tabular representation.
string csv = models.ToCsv();
You can configure headers, delimiters, Excel-friendly quoting, and excluded properties.
string csv = users.ToCsv(configuration =>
{
configuration.ForExcel = true;
configuration.UseExtendedName = false;
configuration.ConfigureHeader(x => x.Id, "Identifier");
configuration.ConfigureHeader(x => x.Groups.First().Name, "GroupName");
configuration.AvoidProperty(x => x.Password);
configuration.Delimiter = ";";
});
Configuration options include:
UseHeaderDelimiterForExcelUseExtendedNameConfigureHeader(...)AvoidProperty(...)ToMinimize() is a compact serializer designed to occupy less space than JSON for many object graphs.
string minimized = models.ToMinimize();
List<CsvModel> restored = minimized.FromMinimization<List<CsvModel>>();
You can also choose the starting separator explicitly.
string minimized = models.ToMinimize('&');
List<CsvModel> restored = minimized.FromMinimization<List<CsvModel>>('&');
Use MinimizationPropertyAttribute when you want deterministic property ordering.
public sealed class CompactUser
{
[MinimizationProperty(0)]
public int Id { get; set; }
[MinimizationProperty(1)]
public string? Name { get; set; }
}
Rystem can serialize expression trees to strings and build them back into executable expressions.
var q = "dasda";
var id = Guid.Parse("bf46510b-b7e6-4ba2-88da-cef208aa81f2");
Expression<Func<MakeIt, bool>> expression =
x => x.X == q &&
x.Samules!.Any(y => y == "ccccde") &&
x.Sol &&
(x.X.Contains(q) || x.Sol.Equals(true)) &&
(x.E == id | x.Id == 32);
string serialized = expression.Serialize();
Func<MakeIt, bool> compiled = serialized.DeserializeAndCompile<MakeIt, bool>();
List<MakeIt> filtered = makes.Where(compiled).ToList();
The test suite covers scenarios such as:
Contains(...)Any(...)DateTime comparisonsTimeSpan comparisons!You can deserialize to LambdaExpression, inspect the inferred result type, convert the return type, and cast back to typed expressions.
Expression<Func<User, int>> selector = x => x.Id;
string text = selector.Serialize();
LambdaExpression dynamicSelector = text.DeserializeAsDynamic<User>();
Expression<Func<User, int>> asInt = dynamicSelector.AsExpression<User, int>();
Expression<Func<User, decimal>> asDecimal = dynamicSelector.AsExpression<User, decimal>();
decimal average = new List<User> { new User { Id = 13 } }.Average(asDecimal.Compile());
var inferred = "x => x.Id == 25".DeserializeAsDynamicAndRetrieveType<User>();
Console.WriteLine(inferred.Type); // System.Boolean
Expression<Func<User, ValueTask<int>>> expression = x => GetUserIdAsync(x);
LambdaExpression lambda = expression;
decimal id = await lambda.InvokeAsync<decimal>(new User { Id = 13 });
InvokeAndTransform(...) is also available when you want a converted return type from a compiled expression.
Expression<Func<User, int>> expression = x => x.Id;
decimal result = expression.InvokeAndTransform<User, int, decimal>(new User { Id = 13 })!;
You can also extract a property directly from an expression.
PropertyInfo? property = ((Expression<Func<User, int>>)(x => x.Id)).GetPropertyFromExpression();
The package exposes dynamic IQueryable operators that accept LambdaExpression instead of strongly typed selectors.
Supported helpers include:
AverageCountDistinctByGroupByLongCountMaxMinOrderByOrderByDescendingSelectSumThenByThenByDescendingWhereExpression<Func<MakeIt, int>> orderExpression = x => x.Id;
Expression<Func<MakeIt, bool>> predicateExpression = x => x.Id >= 10;
LambdaExpression orderBy = orderExpression.Serialize().DeserializeAsDynamic<MakeIt>();
LambdaExpression predicate = predicateExpression.Serialize().DeserializeAsDynamic<MakeIt>();
var query = makes.AsQueryable();
var ordered = query.OrderByDescending(orderBy).ThenBy(orderBy).ToList();
var filtered = query.Where(predicate).ToList();
var grouped = query.GroupBy(orderBy).ToList();
var projected = query.Select<MakeIt, decimal>(orderBy).ToList();
var average = query.Average(orderBy);
var sum = query.Sum(orderBy);
var count = query.Count(predicate);
CallMethodAsync(...) lets you invoke custom async query operators dynamically.
public static class QueryableExtensions
{
public static async Task<IQueryable<T>> GetAsync<T>(
this IQueryable<T> queryable,
Expression<Func<T, bool>> expression,
CancellationToken cancellationToken = default)
{
await Task.Delay(0);
return queryable.Where(expression);
}
}
var result = await query.CallMethodAsync<MakeIt, IQueryable<MakeIt>>(
"GetAsync",
predicate,
typeof(QueryableExtensions));
This is especially useful when you need to bridge runtime-generated selectors with LINQ providers such as Entity Framework.
RemoveWhere(...) works on arrays, ICollection<T>, and plain IEnumerable<T>.
int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
numbers = numbers.RemoveWhere(x => x == 8 || x == 9);
var filtered = myEnumerable.RemoveWhere(x => x.IsExpired);
List<Order> orders = GetOrders();
int removed = orders.RemoveWhere(x => x.Status == OrderStatus.Cancelled);
Use async predicates over a synchronous IEnumerable<T>.
bool allValid = await items.AllAsync(x => ValueTask.FromResult(x.Id >= 0));
bool anyZero = await items.AnyAsync(x => Task.FromResult(x.Id == 0));
Both Task<bool> and ValueTask<bool> predicates are supported.
The non-generic helpers are useful when you receive IEnumerable at runtime and still need indexed operations.
IEnumerable items = GetItems();
object? value = items.ElementAt(10);
items.SetElementAt(10, newValue);
bool removed = items.RemoveElementAt(10, out IEnumerable newItems, out object? removedValue);
They work with both Array and IList implementations.
ReflectionHelper lives in Rystem.Reflection.
using Rystem.Reflection;
string name = ReflectionHelper.NameOfCallingClass(deep: 1);
string fullName = ReflectionHelper.NameOfCallingClass(deep: 1, full: true);
Increase deep when you want to walk further up the call stack.
The package caches repeated reflection lookups for better reuse.
PropertyInfo[] properties = typeof(MyType).FetchProperties();
ConstructorInfo[] constructors = typeof(MyType).FecthConstructors();
FieldInfo[] fields = typeof(MyType).FetchFields();
MethodInfo[] methods = typeof(MyType).FetchMethods();
MethodInfo[] staticMethods = typeof(MyType).FetchStaticMethods();
FetchProperties(...) can ignore properties decorated with specific attributes.
PropertyInfo[] visibleProperties = typeof(MyType).FetchProperties(typeof(JsonIgnoreAttribute));
bool sameOrSon = typeof(Folli).IsTheSameTypeOrASon(typeof(Sulo));
bool sameOrParent = typeof(Sulo).IsTheSameTypeOrAParent(typeof(Folli));
bool hasInterface = typeof(MyService).HasInterface<IDisposable>();
Instance overloads are available too.
Zalo zalo = new();
Sulo sulo = new();
Console.WriteLine(zalo.IsTheSameTypeOrASon(sulo));
Console.WriteLine(sulo.IsTheSameTypeOrAParent(zalo));
CreateWithDefault() builds an instance by recursively creating default constructor arguments and common collection implementations.
public sealed class Foo
{
public IEnumerable<string> Values { get; }
public Foo(IEnumerable<string> values) => Values = values;
}
Foo foo = typeof(Foo).CreateWithDefault<Foo>()!;
(foo.Values as List<string>)!.Add("aaa");
CreateWithDefaultConstructorPropertiesAndField() goes further and populates settable properties and fields as well.
Foo2 foo = typeof(Foo2).CreateWithDefaultConstructorPropertiesAndField<Foo2>()!;
foo.Complex.Add("x", "y");
(foo.Tiny.Values as List<string>)!.Add("aaa");
Rystem can generate runtime implementations for abstract classes and interfaces.
public abstract class Alzio
{
private protected string X { get; }
public string O => X;
public string A { get; set; }
protected Alzio(string x) => X = x;
}
var mocked = typeof(Alzio).CreateInstance("AAA") as Alzio;
mocked!.A = "rrrr";
Console.WriteLine(mocked.O); // AAA
Console.WriteLine(mocked.A); // rrrr
You can also configure the generated type.
Alzio alzio = null!;
var configured = alzio.CreateInstance(configuration =>
{
configuration.IsSealed = false;
configuration.CreateNewOneIfExists = true;
}, "AAA");
ConstructWithBestDynamicFit(...) matches runtime arguments first against constructor parameters and then against settable properties.
var entity = (MySuperClass)typeof(MySuperClass).ConstructWithBestDynamicFit(3, 4, 5, 6)!;
var entity2 = Constructor.InvokeWithBestDynamicFit<MySuperClass>(5, 6, 7, 8);
var interfaceInstance = Constructor.InvokeWithBestDynamicFit<IMogalo>(9, 10, 11, 21);
This is useful when the values are only known at runtime and you still want a best-effort, exact-type match.
IsNullable() works on properties, fields, and parameters.
var type = typeof(InModel);
var constructorParameters = type.GetConstructors().First().GetParameters();
var methodParameters = type.GetMethod(nameof(InModel.SetSomething))!.GetParameters();
var properties = type.GetProperties();
var fields = type.GetFields();
Console.WriteLine(constructorParameters[0].IsNullable());
Console.WriteLine(methodParameters[0].IsNullable());
Console.WriteLine(properties[0].IsNullable());
Console.WriteLine(fields[0].IsNullable());
ToShowcase() builds a structured description of a type and can attach extra computed metadata to each flattened property.
var showcase = typeof(Something).ToShowcase(
IFurtherParameter.Create("Bootstrap", x => new BootstrapProperty(x)),
IFurtherParameter.Create("Title", x => x.NavigationPath));
var first = showcase.FlatProperties.First();
string title = first.GetProperty<string>("Title");
BootstrapProperty bootstrap = first.GetProperty<BootstrapProperty>("Bootstrap");
This is handy for metadata-driven UI generation, forms, and schema exploration.
Inspect the body of a method as text or as decoded IL instructions.
MethodInfo method = typeof(Sulo).GetMethod(nameof(Sulo.Something), BindingFlags.Public | BindingFlags.Instance)!;
string body = method.GetBodyAsString();
List<ILInstruction> instructions = method.GetInstructions();
string signature = method.ToSignature();
This is useful for diagnostics, analysis tools, or advanced runtime inspection.
Create types dynamically at runtime.
var modelName = "MyBestModel";
Type modelType = Model
.Create(modelName)
.AddProperty("Primary", typeof(int))
.AddProperty("Secondary", typeof(bool))
.AddProperty("Name", typeof(string))
.AddProperty("Id", typeof(Guid))
.AddProperty("InModel", typeof(InModel))
.AddParent<SomethingNew>()
.Build();
dynamic instance = Model.Construct(modelName);
instance.Primary = 45;
instance.B = "Aloa";
This gives you a runtime-generated type plus a strongly discoverable builder API.
Generics.With(...) and Generics.WithStatic(...) let you bind generic methods by runtime type and invoke them later.
var staticResult = await Generics
.WithStatic<SystemReflection>(nameof(StaticCreateAsync), typeof(int))
.InvokeAsync(3);
var host = new SystemReflection();
var instanceResult = await Generics
.With<SystemReflection>(nameof(CreateAsync), typeof(int))
.InvokeAsync(host, 3);
int value = Generics
.WithStatic<SystemReflection>(nameof(StaticCreate), typeof(int))
.Invoke<int>(3)!;
NoContext() is a small convenience wrapper around ConfigureAwait(...), controlled by RystemTask.WaitYourStartingThread.
await DoSomethingAsync().NoContext();
int result = GetValueAsync().ToResult();
If you need to preserve the original synchronization context, set:
RystemTask.WaitYourStartingThread = true;
The package adds a small IAsyncEnumerable<T> helper.
await using var stream = "line 1\nline 2\nline 3".ToStream();
List<string> lines = await stream.ReadLinesAsync().ToListAsync();
TaskManager helps you run a bounded number of tasks concurrently.
var bag = new ConcurrentBag<int>();
await TaskManager.WhenAll(ExecuteAsync, times: 45, concurrentTasks: 12, runEverytimeASlotIsFree: true).NoContext();
async Task ExecuteAsync(int i, CancellationToken cancellationToken)
{
await Task.Delay(i * 20, cancellationToken).NoContext();
bag.Add(i);
}
It also works with collections of objects.
await TaskManager.WhenAll(ProcessAsync, orders, concurrentTasks: 5).NoContext();
async Task ProcessAsync(Order order, CancellationToken cancellationToken)
{
await SaveAsync(order, cancellationToken).NoContext();
}
WhenAtLeast(...) stops waiting as soon as the requested number of tasks has completed.
var bag = new ConcurrentBag<int>();
await TaskManager.WhenAtLeast(ExecuteAsync, times: 45, atLeast: 16, concurrentTasks: 12).NoContext();
async Task ExecuteAsync(int i, CancellationToken cancellationToken)
{
await Task.Delay(i * 20, cancellationToken).NoContext();
bag.Add(i);
}
ConcurrentList<T> is a small thread-safe wrapper around List<T> for common list operations.
var items = new ConcurrentList<MyClass>();
items.Add(new MyClass());
items.Insert(0, new MyClass());
items.RemoveAt(0);
Console.WriteLine(items.Count);
Use it when you want a simple IList<T> implementation with locking around the common mutating APIs.
AsyncEnumerable<T>.Empty gives you a typed empty IAsyncEnumerable<T>.
using System.Collection.Generics;
IAsyncEnumerable<MyClass> empty = AsyncEnumerable<MyClass>.Empty;
await foreach (var item in empty)
{
// never reached
}
The package can convert CLR types into other language representations. Right now it supports TypeScript.
using System.ProgrammingLanguage;
ProgrammingLanguangeResponse ts = typeof(MyDto)
.ConvertAs(ProgrammingLanguageType.Typescript);
Console.WriteLine(ts.Text);
Console.WriteLine(ts.MimeType);
ProgrammingLanguangeResponse ts = new[] { typeof(OrderDto), typeof(ProductDto) }
.ConvertAs(ProgrammingLanguageType.Typescript);
ProgrammingLanguangeResponse renamed = typeof(MyInternalClass)
.ConvertAs(ProgrammingLanguageType.Typescript, "PublicDto");
The generated output understands common primitives, arrays, enumerables, dictionaries, enums, and nested types. It also respects JsonPropertyNameAttribute when present.
If you want to see more real-world examples, the best references are the repository tests:
The current README is intentionally long because this package covers a lot of independent utilities.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 net10.0 is compatible. net10.0-android net10.0-android was computed. net10.0-browser net10.0-browser was computed. net10.0-ios net10.0-ios was computed. net10.0-maccatalyst net10.0-maccatalyst was computed. net10.0-macos net10.0-macos was computed. net10.0-tvos net10.0-tvos was computed. net10.0-windows net10.0-windows was computed. |
Showing the top 2 NuGet packages that depend on Rystem:
| Package | Downloads |
|---|---|
|
Rystem.DependencyInjection
Rystem is a open-source framework to improve the System namespace in .Net |
|
|
Rystem.Authentication.Social.Blazor
Rystem.Authentication.Social helps you to integrate with new .Net Identity system and social logins. |
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 10.0.8 | 6,635 | 5/13/2026 |
| 10.0.7 | 180 | 3/26/2026 |
| 10.0.6 | 434,684 | 3/3/2026 |
| 10.0.5 | 568 | 2/21/2026 |
| 10.0.4 | 3,151 | 2/9/2026 |
| 10.0.3 | 148,512 | 1/28/2026 |
| 10.0.2 | 163 | 1/28/2026 |
| 10.0.1 | 210,032 | 11/12/2025 |
| 10.0.0 | 368 | 11/11/2025 |
| 9.1.3 | 999 | 9/2/2025 |
| 9.1.2 | 765,590 | 5/29/2025 |
| 9.1.1 | 103,207 | 5/2/2025 |
| 9.0.32 | 187,518 | 4/15/2025 |
| 9.0.31 | 6,552 | 4/2/2025 |
| 9.0.30 | 89,402 | 3/26/2025 |
| 9.0.29 | 9,722 | 3/18/2025 |
| 9.0.28 | 853 | 3/17/2025 |
| 9.0.27 | 755 | 3/16/2025 |
| 9.0.26 | 1,151 | 3/12/2025 |
| 9.0.25 | 52,706 | 3/9/2025 |