A:初版提交

This commit is contained in:
jimmy committed 2025-11-13 18:14:51 +08:00
1 parent c9778a3e7c
commit 43ff9bc57f
21 files changed
+1655

No files matched your search

+9
View File
@@ -0,0 +1,9 @@
<Application x:Class="Client.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Client"
StartupUri="MainWindow.xaml">
<Application.Resources>
</Application.Resources>
</Application>
+14
View File
@@ -0,0 +1,14 @@
using System.Configuration;
using System.Data;
using System.Windows;
namespace Client
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
}
}
+10
View File
@@ -0,0 +1,10 @@
using System.Windows;
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]
+11
View File
@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UseWPF>true</UseWPF>
</PropertyGroup>
</Project>
+4
View File
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup />
</Project>
+12
View File
@@ -0,0 +1,12 @@
<Window x:Class="Client.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Client"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
</Grid>
</Window>
+24
View File
@@ -0,0 +1,24 @@
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace Client
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
}
+7
View File
@@ -0,0 +1,7 @@
namespace Common
{
public class Class1
{
}
}
+9
View File
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
@@ -0,0 +1,13 @@
namespace Common.ExpandException;
public class ExampleExceptionEnum
{
// region 系统常见错误本地化
/// <summary>
/// 进程信息获取失败
/// </summary>
public static readonly ExceptionExpand ProcessGetException = new(100001L, "进程消息获取失败,指定线程可能不存在或已关闭!", "以管理员权限重试!");
// endregion
}
@@ -0,0 +1,38 @@
namespace Common.ExpandException;
public class ExceptionExpand : Exception
{
/// <summary>
/// 默认异常编码
/// </summary>
private const long DefaultCode = -1;
public string ErrorMessage { get; private set; } = "";
public long ErrorCode { get; private set; } = DefaultCode;
public string Suggestion { get; private set; } = "";
private ExceptionExpand()
{
}
public ExceptionExpand(long errorCode, string message, string solution) : base(
message)
{
ErrorCode = errorCode;
Suggestion = solution;
}
public ExceptionExpand(long errorCode, string message, string solution, Exception? innerException) : base(
message, innerException)
{
ErrorCode = errorCode;
Suggestion = solution;
}
public override string ToString()
{
return $"Error Code: {ErrorCode}\nMessage: {Message}\nSuggestion: {Suggestion}\n{base.ToString()}";
}
}
@@ -0,0 +1,58 @@
namespace Common.ExpandFun;
public static class StringExpand
{
/// <summary>
/// 检测是否是 url
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
public static bool IsValidUrl(this string str)
{
return Uri.TryCreate(str, UriKind.Absolute, out _);
}
public static int ToInt(this string? str, int defaultValue = default)
{
return TryToInt(str) ?? defaultValue;
}
public static int? TryToInt(this string? str)
{
if (String.IsNullOrWhiteSpace(str))
{
return null;
}
try
{
return int.Parse(str);
}
catch (Exception e)
{
return null;
}
}
public static double ToDouble(this string? str, double defaultValue = default)
{
return TryToDouble(str) ?? defaultValue;
}
public static double? TryToDouble(this string? str)
{
if (String.IsNullOrWhiteSpace(str))
{
return null;
}
try
{
return double.Parse(str);
}
catch (Exception e)
{
return null;
}
}
}
+419
View File
@@ -0,0 +1,419 @@
using System.Collections.Concurrent;
namespace Common;
/// <summary>
/// 服务生命周期
/// </summary>
public enum ServiceLifetime
{
/// <summary>
/// 单例:整个应用程序生命周期内只创建一个实例
/// </summary>
Singleton,
/// <summary>
/// 瞬态:每次请求都创建新实例
/// </summary>
Transient
}
/// <summary>
/// 服务变更事件参数
/// </summary>
public class ServiceChangedEventArgs<T> : EventArgs
{
/// <summary>
/// 旧值
/// </summary>
public T? OldValue { get; }
/// <summary>
/// 新值
/// </summary>
public T NewValue { get; }
/// <summary>
/// 变更时间
/// </summary>
public DateTime ChangedAt { get; }
public ServiceChangedEventArgs(T? oldValue, T newValue)
{
OldValue = oldValue;
NewValue = newValue;
ChangedAt = DateTime.Now;
}
}
/// <summary>
/// 轻量级依赖注入容器(线程安全)
/// </summary>
/// <remarks>
/// 支持功能:
/// - 线程安全的服务注册和获取
/// - 生命周期管理(Singleton/Transient)
/// - 服务变更通知机制
/// - 支持工厂方法注册
/// </remarks>
public static class Global
{
private class ServiceDescriptor
{
public Type ServiceType { get; }
public ServiceLifetime Lifetime { get; }
public Func<object> Factory { get; }
public object? SingletonInstance { get; set; }
public readonly object Lock = new();
public ServiceDescriptor(Type serviceType, Func<object> factory, ServiceLifetime lifetime)
{
ServiceType = serviceType;
Factory = factory;
Lifetime = lifetime;
}
}
// 服务描述符存储(线程安全)
private static readonly ConcurrentDictionary<Type, ServiceDescriptor> _services = new();
// 变更通知订阅者存储(线程安全)
private static readonly ConcurrentDictionary<Type, List<Delegate>> _subscribers = new();
// 订阅者列表的锁
private static readonly ConcurrentDictionary<Type, object> _subscriberLocks = new();
#region 注册服务
/// <summary>
/// 注册单例服务实例
/// </summary>
/// <typeparam name="T">服务类型</typeparam>
/// <param name="instance">服务实例</param>
/// <param name="notifyIfExists">如果已存在是否触发变更通知(默认false)</param>
public static void RegisterSingleton<T>(T instance, bool notifyIfExists = false) where T : class
{
if (instance == null)
throw new ArgumentNullException(nameof(instance));
var descriptor = new ServiceDescriptor(
typeof(T),
() => instance,
ServiceLifetime.Singleton)
{
SingletonInstance = instance
};
T? oldValue = null;
var isUpdate = false;
// 如果已存在,保存旧值用于通知
if (notifyIfExists && _services.TryGetValue(typeof(T), out var existingDescriptor))
{
oldValue = existingDescriptor.SingletonInstance as T;
isUpdate = true;
}
_services[typeof(T)] = descriptor;
// 如果是更新且需要通知,触发变更事件
if (isUpdate && notifyIfExists)
{
NotifyChanged(oldValue, instance);
}
}
/// <summary>
/// 注册单例服务(使用工厂方法)
/// </summary>
/// <typeparam name="T">服务类型</typeparam>
/// <param name="factory">创建服务实例的工厂方法</param>
public static void RegisterSingleton<T>(Func<T> factory) where T : class
{
if (factory == null)
throw new ArgumentNullException(nameof(factory));
var descriptor = new ServiceDescriptor(
typeof(T),
() => factory()!,
ServiceLifetime.Singleton);
_services[typeof(T)] = descriptor;
}
/// <summary>
/// 注册瞬态服务(每次获取都创建新实例)
/// </summary>
/// <typeparam name="T">服务类型</typeparam>
/// <param name="factory">创建服务实例的工厂方法</param>
public static void RegisterTransient<T>(Func<T> factory) where T : class
{
if (factory == null)
throw new ArgumentNullException(nameof(factory));
var descriptor = new ServiceDescriptor(
typeof(T),
() => factory()!,
ServiceLifetime.Transient);
_services[typeof(T)] = descriptor;
}
/// <summary>
/// 注册服务(指定生命周期)
/// </summary>
/// <typeparam name="T">服务类型</typeparam>
/// <param name="factory">创建服务实例的工厂方法</param>
/// <param name="lifetime">服务生命周期</param>
public static void Register<T>(Func<T> factory, ServiceLifetime lifetime) where T : class
{
switch (lifetime)
{
case ServiceLifetime.Singleton:
RegisterSingleton(factory);
break;
case ServiceLifetime.Transient:
RegisterTransient(factory);
break;
default:
throw new ArgumentException($"Unknown lifetime: {lifetime}", nameof(lifetime));
}
}
#endregion
#region 获取服务
/// <summary>
/// 获取服务实例
/// </summary>
/// <typeparam name="T">服务类型</typeparam>
/// <returns>服务实例</returns>
/// <exception cref="InvalidOperationException">服务未注册</exception>
public static T Get<T>() where T : class
{
if (!_services.TryGetValue(typeof(T), out var descriptor))
throw new InvalidOperationException($"No service registered for type {typeof(T).Name}");
return (T)ResolveService(descriptor);
}
/// <summary>
/// 尝试获取服务实例
/// </summary>
/// <typeparam name="T">服务类型</typeparam>
/// <param name="instance">服务实例(如果获取失败则为 null)</param>
/// <returns>是否成功获取</returns>
public static bool TryGet<T>(out T? instance) where T : class
{
if (_services.TryGetValue(typeof(T), out var descriptor))
{
instance = (T)ResolveService(descriptor);
return true;
}
instance = null;
return false;
}
/// <summary>
/// 获取服务实例或返回默认值
/// </summary>
/// <typeparam name="T">服务类型</typeparam>
/// <param name="defaultValue">默认值</param>
/// <returns>服务实例或默认值</returns>
public static T? GetOrDefault<T>(T? defaultValue = null) where T : class
{
return TryGet<T>(out var instance) ? instance : defaultValue;
}
private static object ResolveService(ServiceDescriptor descriptor)
{
if (descriptor.Lifetime == ServiceLifetime.Singleton)
{
// 双重检查锁定模式,保证单例只创建一次
if (descriptor.SingletonInstance == null)
{
lock (descriptor.Lock)
{
if (descriptor.SingletonInstance == null)
{
descriptor.SingletonInstance = descriptor.Factory();
}
}
}
return descriptor.SingletonInstance;
}
// Transient: 每次都创建新实例
return descriptor.Factory();
}
#endregion
#region 更新服务
/// <summary>
/// 更新单例服务实例(会触发变更通知)
/// </summary>
/// <typeparam name="T">服务类型</typeparam>
/// <param name="newInstance">新的服务实例</param>
/// <exception cref="InvalidOperationException">服务未注册或不是单例</exception>
public static void Update<T>(T newInstance) where T : class
{
if (newInstance == null)
throw new ArgumentNullException(nameof(newInstance));
if (!_services.TryGetValue(typeof(T), out var descriptor))
throw new InvalidOperationException($"No service registered for type {typeof(T).Name}");
if (descriptor.Lifetime != ServiceLifetime.Singleton)
throw new InvalidOperationException($"Cannot update non-singleton service of type {typeof(T).Name}");
T? oldValue;
lock (descriptor.Lock)
{
oldValue = descriptor.SingletonInstance as T;
descriptor.SingletonInstance = newInstance;
}
// 触发变更通知
NotifyChanged(oldValue, newInstance);
}
#endregion
#region 变更通知
/// <summary>
/// 订阅服务变更通知
/// </summary>
/// <typeparam name="T">服务类型</typeparam>
/// <param name="handler">变更处理程序</param>
public static void Subscribe<T>(Action<ServiceChangedEventArgs<T>> handler) where T : class
{
if (handler == null)
throw new ArgumentNullException(nameof(handler));
var type = typeof(T);
var lockObj = _subscriberLocks.GetOrAdd(type, _ => new object());
lock (lockObj)
{
var subscribers = _subscribers.GetOrAdd(type, _ => new List<Delegate>());
subscribers.Add(handler);
}
}
/// <summary>
/// 取消订阅服务变更通知
/// </summary>
/// <typeparam name="T">服务类型</typeparam>
/// <param name="handler">变更处理程序</param>
public static void Unsubscribe<T>(Action<ServiceChangedEventArgs<T>> handler) where T : class
{
if (handler == null)
throw new ArgumentNullException(nameof(handler));
var type = typeof(T);
if (!_subscriberLocks.TryGetValue(type, out var lockObj))
return;
lock (lockObj)
{
if (_subscribers.TryGetValue(type, out var subscribers))
{
subscribers.Remove(handler);
}
}
}
private static void NotifyChanged<T>(T? oldValue, T newValue) where T : class
{
var type = typeof(T);
if (!_subscribers.TryGetValue(type, out var subscribers))
return;
var eventArgs = new ServiceChangedEventArgs<T>(oldValue, newValue);
// 获取订阅者列表的快照,避免在通知过程中锁定
List<Delegate> subscribersCopy;
if (_subscriberLocks.TryGetValue(type, out var lockObj))
{
lock (lockObj)
{
subscribersCopy = new List<Delegate>(subscribers);
}
}
else
{
subscribersCopy = new List<Delegate>(subscribers);
}
// 通知所有订阅者
foreach (var subscriber in subscribersCopy)
{
try
{
(subscriber as Action<ServiceChangedEventArgs<T>>)?.Invoke(eventArgs);
}
catch
{
// 忽略订阅者抛出的异常,避免影响其他订阅者
// 在实际应用中,可以记录日志
}
}
}
#endregion
#region 管理方法
/// <summary>
/// 检查服务是否已注册
/// </summary>
/// <typeparam name="T">服务类型</typeparam>
/// <returns>是否已注册</returns>
public static bool IsRegistered<T>() where T : class
{
return _services.ContainsKey(typeof(T));
}
/// <summary>
/// 注销服务
/// </summary>
/// <typeparam name="T">服务类型</typeparam>
/// <returns>是否成功注销</returns>
public static bool Unregister<T>() where T : class
{
var type = typeof(T);
var removed = _services.TryRemove(type, out _);
// 同时移除相关的订阅者
if (removed)
{
_subscribers.TryRemove(type, out _);
_subscriberLocks.TryRemove(type, out _);
}
return removed;
}
/// <summary>
/// 清除所有已注册的服务
/// </summary>
public static void Clear()
{
_services.Clear();
_subscribers.Clear();
_subscriberLocks.Clear();
}
/// <summary>
/// 获取已注册的服务数量
/// </summary>
public static int Count => _services.Count;
#endregion
}
+229
View File
@@ -0,0 +1,229 @@
using Common.ExpandException;
namespace Common.Results;
public abstract class Result
{
public bool IsSuccess { get; }
public bool IsFailure => !IsSuccess;
public Exception? Exception { get; }
public string? ErrorMessage { get; }
public long? ErrorCode { get; }
protected Result(bool isSuccess, Exception? exception = null, string? errorMessage = null, long? errorCode = null)
{
if (isSuccess && exception is not null)
throw new ArgumentException("Success result cannot have an exception", nameof(exception));
if (isSuccess && errorCode.HasValue)
throw new ArgumentException("Success result cannot have an error code", nameof(errorCode));
if (!isSuccess && exception is null && string.IsNullOrEmpty(errorMessage) && !errorCode.HasValue)
throw new ArgumentException("Failed result must have either an exception, error message, or error code");
IsSuccess = isSuccess;
Exception = exception;
ErrorMessage = errorMessage ?? exception?.Message;
ErrorCode = errorCode;
// If we have an ExceptionExpand, extract the error code from it
if (exception is ExceptionExpand exceptionExpand && !errorCode.HasValue)
{
ErrorCode = exceptionExpand.ErrorCode;
}
}
public static Result Success() => new SimpleResult(true);
public static Result<T> Success<T>(T value) => new Result<T>(value);
public static Result Failure(string errorMessage) => new SimpleResult(false, null, errorMessage);
public static Result Failure(Exception exception) => new SimpleResult(false, exception);
public static Result Failure(long errorCode, string errorMessage) => new SimpleResult(false, null, errorMessage, errorCode);
public static Result Failure(long errorCode, string errorMessage, Exception exception) => new SimpleResult(false, exception, errorMessage, errorCode);
public static Result<T> Failure<T>(string errorMessage) => new Result<T>(default, false, null, errorMessage);
public static Result<T> Failure<T>(Exception exception) => new Result<T>(default, false, exception);
public static Result<T> Failure<T>(long errorCode, string errorMessage) => new Result<T>(default, false, null, errorMessage, errorCode);
public static Result<T> Failure<T>(long errorCode, string errorMessage, Exception exception) => new Result<T>(default, false, exception, errorMessage, errorCode);
public static Result Try(Action action)
{
try
{
action();
return Success();
}
catch (Exception ex)
{
return Failure(ex);
}
}
public static Result<T> Try<T>(Func<T> func)
{
try
{
var result = func();
return Success(result);
}
catch (Exception ex)
{
return Failure<T>(ex);
}
}
public static async Task<Result> TryAsync(Func<Task> taskFunc)
{
try
{
await taskFunc();
return Success();
}
catch (Exception ex)
{
return Failure(ex);
}
}
public static async Task<Result<T>> TryAsync<T>(Func<Task<T>> taskFunc)
{
try
{
var result = await taskFunc();
return Success(result);
}
catch (Exception ex)
{
return Failure<T>(ex);
}
}
public Result OnSuccess(Action action)
{
if (IsSuccess)
action();
return this;
}
public Result OnFailure(Action<Exception?, string?, long?> action)
{
if (IsFailure)
action(Exception, ErrorMessage, ErrorCode);
return this;
}
public Result OnFailure(Action<Exception?, string?> action)
{
if (IsFailure)
action(Exception, ErrorMessage);
return this;
}
public Result<T> Match<T>(Func<T> onSuccess, Func<Exception?, string?, long?, T> onFailure)
{
return IsSuccess ? Result.Success(onSuccess()) : Result.Failure<T>(Exception ?? new Exception(ErrorMessage));
}
public Result<T> Match<T>(Func<T> onSuccess, Func<Exception?, string?, T> onFailure)
{
return IsSuccess ? Result.Success(onSuccess()) : Result.Failure<T>(Exception ?? new Exception(ErrorMessage));
}
private class SimpleResult : Result
{
public SimpleResult(bool isSuccess, Exception? exception = null, string? errorMessage = null, long? errorCode = null)
: base(isSuccess, exception, errorMessage, errorCode)
{
}
}
}
public class Result<T> : Result
{
public T? Value { get; }
public Result(T? value, bool isSuccess = true, Exception? exception = null, string? errorMessage = null, long? errorCode = null)
: base(isSuccess, exception, errorMessage, errorCode)
{
Value = value;
}
public static implicit operator Result<T>(T value) => new Result<T>(value);
public Result<TNew> Map<TNew>(Func<T, TNew> mapper)
{
if (IsFailure)
{
if (ErrorCode.HasValue)
return Result.Failure<TNew>(ErrorCode.Value, ErrorMessage ?? "Mapping failed", Exception);
return Result.Failure<TNew>(Exception ?? new Exception(ErrorMessage));
}
try
{
var mappedValue = mapper(Value!);
return Result.Success(mappedValue);
}
catch (Exception ex)
{
return Result.Failure<TNew>(ex);
}
}
public Result<TNew> Bind<TNew>(Func<T, Result<TNew>> binder)
{
if (IsFailure)
{
if (ErrorCode.HasValue)
return Result.Failure<TNew>(ErrorCode.Value, ErrorMessage ?? "Binding failed", Exception);
return Result.Failure<TNew>(Exception ?? new Exception(ErrorMessage));
}
try
{
return binder(Value!);
}
catch (Exception ex)
{
return Result.Failure<TNew>(ex);
}
}
public Result OnSuccess(Action<T> action)
{
if (IsSuccess)
action(Value!);
return this;
}
public new Result<T> OnFailure(Action<Exception?, string?, long?> action)
{
if (IsFailure)
action(Exception, ErrorMessage, ErrorCode);
return this;
}
public new Result<T> OnFailure(Action<Exception?, string?> action)
{
if (IsFailure)
action(Exception, ErrorMessage);
return this;
}
public TResult Match<TResult>(Func<T, TResult> onSuccess, Func<Exception?, string?, long?, TResult> onFailure)
{
return IsSuccess ? onSuccess(Value!) : onFailure(Exception, ErrorMessage, ErrorCode);
}
public TResult Match<TResult>(Func<T, TResult> onSuccess, Func<Exception?, string?, TResult> onFailure)
{
return IsSuccess ? onSuccess(Value!) : onFailure(Exception, ErrorMessage);
}
}
@@ -0,0 +1,294 @@
using Common.ExpandException;
namespace Common.Results;
public static class ResultExtensions
{
public static T? GetOrNull<T>(this Result<T> result) where T : class
{
return result.IsSuccess ? result.Value : null;
}
public static T? GetOrNullable<T>(this Result<T> result) where T : struct
{
return result.IsSuccess ? result.Value : null;
}
public static T GetOrDefault<T>(this Result<T> result, T defaultValue = default!)
{
return result.IsSuccess ? result.Value! : defaultValue;
}
public static T GetOrThrow<T>(this Result<T> result)
{
if (result.IsSuccess)
return result.Value!;
if (result.Exception is not null)
throw result.Exception;
throw new InvalidOperationException(result.ErrorMessage ?? "Operation failed");
}
public static Exception? ExceptionOrNull(this Result result)
{
return result.IsFailure ? result.Exception : null;
}
public static string? ErrorMessageOrNull(this Result result)
{
return result.IsFailure ? result.ErrorMessage : null;
}
public static long? ErrorCodeOrNull(this Result result)
{
return result.IsFailure ? result.ErrorCode : null;
}
public static T GetOrElse<T>(this Result<T> result, Func<T> alternativeProvider)
{
return result.IsSuccess ? result.Value! : alternativeProvider();
}
public static T GetOrElse<T>(this Result<T> result, Func<Exception?, string?, T> alternativeProvider)
{
return result.IsSuccess ? result.Value! : alternativeProvider(result.Exception, result.ErrorMessage);
}
public static T GetOrElse<T>(this Result<T> result, Func<Exception?, string?, long?, T> alternativeProvider)
{
return result.IsSuccess ? result.Value! : alternativeProvider(result.Exception, result.ErrorMessage, result.ErrorCode);
}
public static Result<T> Where<T>(this Result<T> result, Func<T, bool> predicate, string errorMessage = "Predicate not satisfied")
{
if (result.IsFailure)
return result;
return predicate(result.Value!) ? result : Result.Failure<T>(errorMessage);
}
public static Result<T> Where<T>(this Result<T> result, Func<T, bool> predicate, long errorCode, string errorMessage = "Predicate not satisfied")
{
if (result.IsFailure)
return result;
return predicate(result.Value!) ? result : Result.Failure<T>(errorCode, errorMessage);
}
public static Result<T> Where<T>(this Result<T> result, Func<T, bool> predicate, Func<T, Exception> exceptionProvider)
{
if (result.IsFailure)
return result;
return predicate(result.Value!) ? result : Result.Failure<T>(exceptionProvider(result.Value!));
}
public static Result<U> SelectMany<T, U>(this Result<T> result, Func<T, Result<U>> selector)
{
return result.Bind(selector);
}
public static Result<V> SelectMany<T, U, V>(this Result<T> result, Func<T, Result<U>> selector, Func<T, U, V> resultSelector)
{
return result.Bind(t => selector(t).Map(u => resultSelector(t, u)));
}
public static Result<U> Select<T, U>(this Result<T> result, Func<T, U> selector)
{
return result.Map(selector);
}
public static Result<T> DoOnSuccess<T>(this Result<T> result, Action<T> action)
{
if (result.IsSuccess)
action(result.Value!);
return result;
}
public static Result<T> DoOnFailure<T>(this Result<T> result, Action<Exception?, string?> action)
{
if (result.IsFailure)
action(result.Exception, result.ErrorMessage);
return result;
}
public static Result<T> DoOnFailure<T>(this Result<T> result, Action<Exception?, string?, long?> action)
{
if (result.IsFailure)
action(result.Exception, result.ErrorMessage, result.ErrorCode);
return result;
}
public static Result<T> DoOnBoth<T>(this Result<T> result, Action<T> onSuccess, Action<Exception?, string?> onFailure)
{
if (result.IsSuccess)
onSuccess(result.Value!);
else
onFailure(result.Exception, result.ErrorMessage);
return result;
}
public static Result<T> DoOnBoth<T>(this Result<T> result, Action<T> onSuccess, Action<Exception?, string?, long?> onFailure)
{
if (result.IsSuccess)
onSuccess(result.Value!);
else
onFailure(result.Exception, result.ErrorMessage, result.ErrorCode);
return result;
}
public static Result Ignore<T>(this Result<T> result)
{
if (result.IsSuccess)
return Result.Success();
if (result.ErrorCode.HasValue)
return Result.Failure(result.ErrorCode.Value, result.ErrorMessage ?? "Operation failed", result.Exception);
return Result.Failure(result.Exception ?? new Exception(result.ErrorMessage));
}
public static Result<T> AsResult<T>(this T value) where T : notnull
{
return Result.Success(value);
}
public static Result<T> AsResultOrFailure<T>(this T? value, string errorMessage = "Value is null") where T : class
{
return value is not null ? Result.Success(value) : Result.Failure<T>(errorMessage);
}
public static Result<T> AsResultOrFailure<T>(this T? value, string errorMessage = "Value is null") where T : struct
{
return value.HasValue ? Result.Success(value.Value) : Result.Failure<T>(errorMessage);
}
public static async Task<Result<T>> DoOnSuccessAsync<T>(this Task<Result<T>> resultTask, Func<T, Task> asyncAction)
{
var result = await resultTask;
if (result.IsSuccess)
await asyncAction(result.Value!);
return result;
}
public static async Task<Result<T>> DoOnFailureAsync<T>(this Task<Result<T>> resultTask, Func<Exception?, string?, Task> asyncAction)
{
var result = await resultTask;
if (result.IsFailure)
await asyncAction(result.Exception, result.ErrorMessage);
return result;
}
public static async Task<Result<U>> MapAsync<T, U>(this Task<Result<T>> resultTask, Func<T, Task<U>> asyncMapper)
{
var result = await resultTask;
if (result.IsFailure)
return Result.Failure<U>(result.Exception ?? new Exception(result.ErrorMessage));
try
{
var mappedValue = await asyncMapper(result.Value!);
return Result.Success(mappedValue);
}
catch (Exception ex)
{
return Result.Failure<U>(ex);
}
}
public static async Task<Result<U>> BindAsync<T, U>(this Task<Result<T>> resultTask, Func<T, Task<Result<U>>> asyncBinder)
{
var result = await resultTask;
if (result.IsFailure)
return Result.Failure<U>(result.Exception ?? new Exception(result.ErrorMessage));
try
{
return await asyncBinder(result.Value!);
}
catch (Exception ex)
{
return Result.Failure<U>(ex);
}
}
public static Result<T> Ensure<T>(this Result<T> result, Func<T, bool> predicate, string errorMessage)
{
if (result.IsFailure)
return result;
return predicate(result.Value!) ? result : Result.Failure<T>(errorMessage);
}
public static Result<T> Ensure<T>(this Result<T> result, Func<T, bool> predicate, long errorCode, string errorMessage)
{
if (result.IsFailure)
return result;
return predicate(result.Value!) ? result : Result.Failure<T>(errorCode, errorMessage);
}
public static Result<T> Ensure<T>(this Result<T> result, Func<T, bool> predicate, Func<T, Exception> exceptionFactory)
{
if (result.IsFailure)
return result;
return predicate(result.Value!) ? result : Result.Failure<T>(exceptionFactory(result.Value!));
}
public static Result<T> OrElse<T>(this Result<T> result, Func<Result<T>> alternativeProvider)
{
return result.IsSuccess ? result : alternativeProvider();
}
public static Result<T> OrElse<T>(this Result<T> result, Func<Exception?, string?, Result<T>> alternativeProvider)
{
return result.IsSuccess ? result : alternativeProvider(result.Exception, result.ErrorMessage);
}
public static Result<T> OrElse<T>(this Result<T> result, Func<Exception?, string?, long?, Result<T>> alternativeProvider)
{
return result.IsSuccess ? result : alternativeProvider(result.Exception, result.ErrorMessage, result.ErrorCode);
}
/// <summary>
/// Checks if the result has the specified error code
/// </summary>
public static bool HasErrorCode(this Result result, long errorCode)
{
return result.IsFailure && result.ErrorCode == errorCode;
}
/// <summary>
/// Gets the error code or returns the default value
/// </summary>
public static long GetErrorCodeOrDefault(this Result result, long defaultValue = -1)
{
return result.ErrorCode ?? defaultValue;
}
/// <summary>
/// Creates a Result failure with an error code
/// </summary>
public static Result<T> FailWithCode<T>(long errorCode, string errorMessage)
{
return Result.Failure<T>(errorCode, errorMessage);
}
/// <summary>
/// Converts an ExceptionExpand to a Result failure
/// </summary>
public static Result<T> FromExceptionExpand<T>(ExceptionExpand exception)
{
return Result.Failure<T>(exception.ErrorCode, exception.Message, exception);
}
/// <summary>
/// Converts an ExceptionExpand to a non-generic Result failure
/// </summary>
public static Result FromExceptionExpand(ExceptionExpand exception)
{
return Result.Failure(exception.ErrorCode, exception.Message, exception);
}
}
@@ -0,0 +1,365 @@
using Common.ExpandException;
namespace Common.Results;
/// <summary>
/// Examples demonstrating how to use the Result《T》 class and its extensions
/// </summary>
public static class ResultUsageExamples
{
/// <summary>
/// Basic usage examples
/// </summary>
public static void BasicUsageExamples()
{
// Creating successful results
var successResult = Result.Success(42);
var voidSuccessResult = Result.Success();
// Creating failed results
var failureResult = Result.Failure<int>("Something went wrong");
var exceptionResult = Result.Failure<int>(new ArgumentException("Invalid argument"));
// Creating failed results with error codes
var errorCodeResult = Result.Failure<int>(404L, "Not found");
var errorCodeWithExceptionResult = Result.Failure<int>(500L, "Internal server error", new Exception("Database connection failed"));
// Using Try patterns for safe execution
var tryResult = Result.Try(() => int.Parse("123"));
var tryFailResult = Result.Try(() => int.Parse("invalid"));
// Async Try patterns
var asyncResult = Result.TryAsync(async () => await SomeAsyncOperation());
}
/// <summary>
/// Extension methods examples
/// </summary>
public static void ExtensionMethodsExamples()
{
var result = Result.Success(42);
// Safe value extraction
var nullableValue = result.GetOrNullable(); // Returns 42 for value types
var defaultValue = result.GetOrDefault(0); // Returns 42
var throwValue = result.GetOrThrow(); // Returns 42
// Alternative providers
var alternative = result.GetOrElse(() => 0);
var altResult = result.OrElse(() => Result.Success(0));
// Conversion methods
string? nullValue = null;
var nullResult = nullValue.AsResultOrFailure(); // Creates failure result
var valueResult = "test".AsResult(); // Creates success result
// Working with failures
var failResult = Result.Failure<int>("error");
var failNullable = failResult.GetOrNullable(); // Returns null
var failDefault = failResult.GetOrDefault(999); // Returns 999
var exception = failResult.ExceptionOrNull(); // Returns null (no exception, just message)
var errorMsg = failResult.ErrorMessageOrNull(); // Returns "error"
// Working with error codes
var errorCodeResult = Result.Failure<int>(404L, "Not found");
var errorCode = errorCodeResult.ErrorCodeOrNull(); // Returns 404
var hasSpecificCode = errorCodeResult.HasErrorCode(404L); // Returns true
var codeOrDefault = errorCodeResult.GetErrorCodeOrDefault(); // Returns 404
}
/// <summary>
/// Functional programming examples
/// </summary>
public static void FunctionalExamples()
{
var result = Result.Success(10);
// Map: Transform successful values
var doubled = result.Map(x => x * 2); // Result<int> with value 20
// Bind: Chain operations that return Results
var chained = result
.Bind(x => x > 5 ? Result.Success(x * 2) : Result.Failure<int>("Too small"))
.Bind(x => Result.Success(x.ToString()));
// Filter with Where/Ensure
var filtered = result
.Where(x => x > 5, "Value must be greater than 5")
.Ensure(x => x < 100, "Value must be less than 100");
// Filter with error codes
var filteredWithCode = result
.Where(x => x > 5, 400L, "Value must be greater than 5")
.Ensure(x => x < 100, 422L, "Value must be less than 100");
// Conditional execution
result.OnSuccess(value => Console.WriteLine($"Value: {value}"));
result.OnFailure((ex, msg) => Console.WriteLine($"Error: {msg}"));
result.OnFailure((ex, msg, code) => Console.WriteLine($"Error {code}: {msg}"));
// Pattern matching
var message = result.Match(
onSuccess: value => $"Success: {value}",
onFailure: (ex, msg) => $"Error: {msg}");
var messageWithCode = result.Match(
onSuccess: value => $"Success: {value}",
onFailure: (ex, msg, code) => $"Error {code}: {msg}");
}
/// <summary>
/// LINQ query syntax examples
/// </summary>
public static void LinqExamples()
{
// Using LINQ query syntax with Results
var result1 = Result.Success(5);
var result2 = Result.Success(10);
var combined = from x in result1
from y in result2
where x > 0 && y > 0
select x + y;
// More complex LINQ operations
var processed = from value in Result.Success("hello")
let upper = value.ToUpper()
where upper.Length > 3
select $"Processed: {upper}";
}
/// <summary>
/// Async operations examples
/// </summary>
public static async Task AsyncExamples()
{
// Async Try patterns
var asyncResult = await Result.TryAsync(async () =>
{
await Task.Delay(100);
return 42;
});
// Working with async results
if (asyncResult.IsSuccess)
{
await Task.Delay(50);
Console.WriteLine($"Processing: {asyncResult.Value}");
}
// Async mapping and binding
var mappedResult = await Task.FromResult(Result.Success(10))
.MapAsync(async value =>
{
await Task.Delay(50);
return value * 2;
});
var boundResult = await Task.FromResult(Result.Success(5))
.BindAsync(async value =>
{
await Task.Delay(50);
return value > 0 ? Result.Success(value * 2) : Result.Failure<int>("Invalid");
});
}
/// <summary>
/// Error handling patterns
/// </summary>
public static void ErrorHandlingExamples()
{
// Multiple validation steps
var input = "123";
var validationResult = input.AsResult()
.Ensure(s => !string.IsNullOrEmpty(s), "Input cannot be empty")
.Bind(s => Result.Try(() => int.Parse(s)))
.Ensure(i => i > 0, "Number must be positive")
.Map(i => i * 2);
// Handling different error scenarios
validationResult
.OnSuccess(value => Console.WriteLine($"Valid result: {value}"))
.OnFailure((ex, msg, code) =>
{
if (ex is FormatException)
Console.WriteLine("Invalid number format");
else if (code.HasValue)
Console.WriteLine($"Validation error {code}: {msg}");
else
Console.WriteLine($"Validation error: {msg}");
});
// Providing fallback values
var safeResult = validationResult.OrElse(() => Result.Success(0));
var finalValue = validationResult.GetOrElse(() => 0);
// Error code specific handling
var safeWithCodeHandling = validationResult.OrElse((ex, msg, code) =>
{
return code switch
{
400L => Result.Success(0), // Bad request -> default
422L => Result.Success(-1), // Validation -> sentinel
_ => Result.Success(999) // Unknown -> special value
};
});
}
/// <summary>
/// Integration with existing code examples
/// </summary>
public static void IntegrationExamples()
{
// Converting existing exception-throwing code
var legacyResult = Result.Try(() => RiskyOperation());
// Working with nullable types
string? nullableString = GetNullableString();
var nullableResult = nullableString.AsResultOrFailure("String was null");
// Converting Result back to exceptions when needed
try
{
var value = legacyResult.GetOrThrow();
// Use value...
}
catch (Exception ex)
{
// Handle exception...
}
// Ignoring typed results when only success/failure matters
var voidResult = Result.Success(42).Ignore();
}
/// <summary>
/// Error code specific examples
/// </summary>
public static void ErrorCodeExamples()
{
// Creating failures with error codes
var notFoundResult = Result.Failure<string>(404L, "Resource not found");
var validationResult = Result.Failure<int>(422L, "Invalid input");
var serverError = Result.Failure<object>(500L, "Internal server error", new Exception("Database connection failed"));
// Using ExceptionExpand with error codes
var customException = new ExceptionExpand(1001L, "Custom business error", "Check your input parameters");
var fromException = ResultExtensions.FromExceptionExpand<string>(customException);
// Error code checking and handling
if (notFoundResult.HasErrorCode(404L))
{
Console.WriteLine("Handle not found error");
}
// Switch on error codes
var errorCode = validationResult.GetErrorCodeOrDefault();
var response = errorCode switch
{
404L => "Resource not found",
422L => "Validation failed",
500L => "Server error",
_ => "Unknown error"
};
// Error code in functional chains
var processedResult = Result.Success("user@example.com")
.Ensure(email => email.Contains("@"), 400L, "Invalid email format")
.Bind(email => ValidateEmailDomain(email))
.Map(email => $"Welcome, {email}");
// Handling specific error codes
processedResult.OnFailure((ex, msg, code) =>
{
var logLevel = code switch
{
400L => "WARN", // Validation errors
404L => "INFO", // Not found
500L => "ERROR", // Server errors
_ => "DEBUG"
};
Console.WriteLine($"[{logLevel}] Error {code}: {msg}");
});
// Creating error code specific results
var unauthorizedResult = ResultExtensions.FailWithCode<string>(401L, "Unauthorized access");
var forbiddenResult = Result.Failure<string>(403L, "Insufficient permissions");
// Combining results with error code preservation
var combinedResult = from user in GetUserById(123)
from permissions in GetUserPermissions(user)
where permissions.CanRead
select user;
}
/// <summary>
/// HTTP status code style error handling
/// </summary>
public static void HttpStatusCodeStyleExamples()
{
// Common HTTP status codes as error codes
var badRequest = Result.Failure<string>(400L, "Bad Request");
var unauthorized = Result.Failure<string>(401L, "Unauthorized");
var forbidden = Result.Failure<string>(403L, "Forbidden");
var notFound = Result.Failure<string>(404L, "Not Found");
var conflict = Result.Failure<string>(409L, "Conflict");
var unprocessable = Result.Failure<string>(422L, "Unprocessable Entity");
var internalError = Result.Failure<string>(500L, "Internal Server Error");
// Business logic error codes
var businessError = Result.Failure<string>(1001L, "Insufficient balance");
var domainError = Result.Failure<string>(2001L, "Invalid user state");
var integrationError = Result.Failure<string>(3001L, "External service unavailable");
// Error code ranges for different error types
var validationError = Result.Failure<string>(4001L, "Required field missing"); // 4xxx: Validation
var businessLogicError = Result.Failure<string>(5001L, "Business rule violation"); // 5xxx: Business
var systemError = Result.Failure<string>(6001L, "Database connection failed"); // 6xxx: System
}
// Helper methods for examples
private static int RiskyOperation() => throw new InvalidOperationException("Something went wrong");
private static string? GetNullableString() => null;
private static async Task<string> SomeAsyncOperation()
{
await Task.Delay(100);
return "async result";
}
private static Result<string> ValidateEmailDomain(string email)
{
var domain = email.Split('@').LastOrDefault();
return domain switch
{
"blocked.com" => Result.Failure<string>(403L, "Domain is blocked"),
null => Result.Failure<string>(400L, "Invalid email format"),
_ => Result.Success(email)
};
}
private static Result<User> GetUserById(int id)
{
return id switch
{
123 => Result.Success(new User { Id = 123, Name = "John" }),
_ => Result.Failure<User>(404L, "User not found")
};
}
private static Result<UserPermissions> GetUserPermissions(User user)
{
return Result.Success(new UserPermissions { CanRead = true, CanWrite = false });
}
private class User
{
public int Id { get; set; }
public string Name { get; set; } = "";
}
private class UserPermissions
{
public bool CanRead { get; set; }
public bool CanWrite { get; set; }
}
}
+42
View File
@@ -0,0 +1,42 @@
using System.Net;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Common.Utils;
public static class JsonHelper
{
public static readonly JsonSerializerOptions CamelCaseOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = true,
Converters =
{
new IpAddressConverter(), new JsonStringEnumConverter(JsonNamingPolicy.CamelCase, allowIntegerValues: false)
},
};
public static string ToJson<T>(T value) =>
JsonSerializer.Serialize(value, CamelCaseOptions);
public static T? FromJson<T>(string json) =>
JsonSerializer.Deserialize<T>(json, CamelCaseOptions);
}
/*
IPAddress.ScopeId 属性在某些情况下(如 IPv4 类型)是不被支持的,会抛出异常。
这是 .NET 的设计问题,访问 ScopeId 只有在 IPv6 地址中才有意义,在 IPv4 中访问它就会抛出这个 10045 错误。
*/
public class IpAddressConverter : JsonConverter<IPAddress>
{
public override IPAddress Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var ipString = reader.GetString() ?? String.Empty;
return IPAddress.Parse(ipString);
}
public override void Write(Utf8JsonWriter writer, IPAddress value, JsonSerializerOptions options)
{
writer.WriteStringValue(value?.ToString());
}
}
+44
View File
@@ -0,0 +1,44 @@
using System.Net;
using System.Net.Sockets;
namespace Common.Utils;
public static class SystemHelper
{
/// <summary>
/// 获取可用端口
/// </summary>
/// <returns></returns>
public static int GetAvailablePort()
{
// todo: 2025/6/24 15:53 开发过程中使用固定端口
// return 8267;
TcpListener listener = new TcpListener(IPAddress.Loopback, 0); // 端口号为0表示由系统自动分配
listener.Start();
int port = ((IPEndPoint)listener.LocalEndpoint).Port;
listener.Stop();
return port;
}
/// <summary>
/// 检查当前进程是否以管理员权限运行
/// </summary>
/// <returns>如果是管理员权限返回 true,否则返回 false</returns>
/// <exception cref="PlatformNotSupportedException">仅支持 Windows 平台</exception>
/// <remarks>
/// 此方法仅适用于 Windows 操作系统。
/// 在非 Windows 平台上会抛出 PlatformNotSupportedException 异常。
/// </remarks>
public static bool IsAdministrator()
{
if (!OperatingSystem.IsWindows())
{
throw new PlatformNotSupportedException("IsAdministrator method is only supported on Windows platform.");
}
var identity = System.Security.Principal.WindowsIdentity.GetCurrent();
var principal = new System.Security.Principal.WindowsPrincipal(identity);
return principal.IsInRole(System.Security.Principal.WindowsBuiltInRole.Administrator);
}
}
+7
View File
@@ -0,0 +1,7 @@
namespace Config
{
public class Class1
{
}
}
+9
View File
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
+37
View File
@@ -0,0 +1,37 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.13.35931.197 d17.13
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Client", "Client\Client.csproj", "{5979868C-C09D-4B3F-8909-C661F94EFFBE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common", "Common\Common.csproj", "{EF054B5F-5D65-4917-B2D7-A85FA2719128}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Config", "Config\Config.csproj", "{37FBE4C9-1658-4953-8D12-71068EE28804}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{5979868C-C09D-4B3F-8909-C661F94EFFBE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5979868C-C09D-4B3F-8909-C661F94EFFBE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5979868C-C09D-4B3F-8909-C661F94EFFBE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5979868C-C09D-4B3F-8909-C661F94EFFBE}.Release|Any CPU.Build.0 = Release|Any CPU
{EF054B5F-5D65-4917-B2D7-A85FA2719128}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EF054B5F-5D65-4917-B2D7-A85FA2719128}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EF054B5F-5D65-4917-B2D7-A85FA2719128}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EF054B5F-5D65-4917-B2D7-A85FA2719128}.Release|Any CPU.Build.0 = Release|Any CPU
{37FBE4C9-1658-4953-8D12-71068EE28804}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{37FBE4C9-1658-4953-8D12-71068EE28804}.Debug|Any CPU.Build.0 = Debug|Any CPU
{37FBE4C9-1658-4953-8D12-71068EE28804}.Release|Any CPU.ActiveCfg = Release|Any CPU
{37FBE4C9-1658-4953-8D12-71068EE28804}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {579DA456-8923-4BFC-862A-E6F97208B6ED}
EndGlobalSection
EndGlobal