Avalonia配置,參考http://www.rzrgm.cn/dalgleish/p/18967204
隨時更新,目前已支持多個cs文件動態(tài)編譯。
AvaloniaExtensions.cs代碼
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Controls.Shapes;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.Markup.Xaml;
using Avalonia.Markup.Xaml.Styling;
using Avalonia.Media;
using Avalonia.Media.Immutable;
using Avalonia.Platform;
using Avalonia.Threading;
using Avalonia.VisualTree;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace Shares.Avalonia
{
public static class AvaloniaExtensions
{
public static IResourceDictionary WindowResources(this Control topLevel)
{
return Application.Current?.Resources!;
}
public static IClassicDesktopStyleApplicationLifetime? GetDesktopLifetime()
{
return Application.Current?.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime;
}
public static Control Create(this Control topLevel, string target, params object[] parameters)
{
var viewType = Type.GetType($"{target}, {target.Split(".")[0]}");
if (viewType == null)
return CreateErrorControl($"報錯:無法找到類型 \"{target}\",請確保其已位于正確命名空間");
return (Control)Activator.CreateInstance(viewType, args: parameters)!;
}
// 緩存已編譯代碼的 Assembly
private static readonly ConcurrentDictionary<string, Assembly> assemblyCache = new();
//單文件編譯
public static async Task<Control> RunXamlAsync(this Control topLevel, string? xaml, string? code, string cls = "SampleView")
{
if (string.IsNullOrWhiteSpace(xaml))
return CreateErrorControl("XAML代碼為空,請?zhí)峁┯行У?XAML。");
string key = $"{xaml.GetHashCode()}_{code?.GetHashCode() ?? 0}_{cls}";
try
{
Assembly? scriptAssembly = null;
object? rootInstance = null;
if (!string.IsNullOrWhiteSpace(code))
{
if (!assemblyCache.TryGetValue(key, out scriptAssembly))
{
var (assembly, _) = await CompilerService.GetScriptAssembly(code);
if (assembly == null)
return CreateErrorControl("C# 代碼編譯失敗,請檢查語法。");
assemblyCache[key] = assembly;
scriptAssembly = assembly;
}
var sampleViewType = scriptAssembly.GetTypes()
.FirstOrDefault(t => t.Name == cls);
if (sampleViewType == null)
return CreateErrorControl($"在編譯結(jié)果中未找到類型 \"{cls}\",請確保定義了該類。");
rootInstance = Activator.CreateInstance(sampleViewType);
}
Control? control;
if (scriptAssembly != null && rootInstance != null)
{
await using var stream = new MemoryStream(Encoding.UTF8.GetBytes(xaml));
control = AvaloniaRuntimeXamlLoader.Load(stream, scriptAssembly, rootInstance) as Control;
}
else
{
control = AvaloniaRuntimeXamlLoader.Parse<Control?>(xaml);
}
if (control is Window w)
{
if (w.Content is string str)
control = new Label() { Content = str };
else
control = (Control)w.Content!;
}
return control ?? CreateErrorControl("XAML加載失敗,返回了空控件。");
}
catch (Exception ex)
{
Console.WriteLine(ex);
return CreateErrorControl("發(fā)生異常:" + ex.Message);
}
}
//多文件編譯
/*
* var codeFiles = new Dictionary<string, string>
{
["SampleView.cs"] = File.ReadAllText("SampleView.cs"),
["SampleViewModel.cs"] = File.ReadAllText("SampleViewModel.cs")
};
*/
public static async Task<Control> RunXamlAsync(this Control topLevel, string? xaml, Dictionary<string, string>? codeFiles, string cls = "SampleView")
{
if (string.IsNullOrWhiteSpace(xaml))
return CreateErrorControl("XAML代碼為空,請?zhí)峁┯行У?XAML。");
string key = $"{xaml.GetHashCode()}_{(codeFiles is null ? 0 : string.Join(",", codeFiles.Keys).GetHashCode())}_{cls}";
try
{
Assembly? scriptAssembly = null;
object? rootInstance = null;
if (codeFiles is not null && codeFiles.Count > 0)
{
if (!assemblyCache.TryGetValue(key, out scriptAssembly))
{
var (assembly, _) = await CompilerService.GetScriptAssembly(codeFiles);
if (assembly == null)
return CreateErrorControl("C# 多文件代碼編譯失敗,請檢查語法。");
assemblyCache[key] = assembly;
scriptAssembly = assembly;
}
var sampleViewType = scriptAssembly.GetTypes()
.FirstOrDefault(t => t.Name == cls);
if (sampleViewType == null)
return CreateErrorControl($"在編譯結(jié)果中未找到類型 \"{cls}\",請確保在源碼中正確定義。");
rootInstance = Activator.CreateInstance(sampleViewType);
}
Control? control;
if (scriptAssembly != null && rootInstance != null)
{
await using var stream = new MemoryStream(Encoding.UTF8.GetBytes(xaml));
control = AvaloniaRuntimeXamlLoader.Load(stream, scriptAssembly, rootInstance) as Control;
}
else
{
control = AvaloniaRuntimeXamlLoader.Parse<Control?>(xaml);
}
if (control is Window w)
{
if (w.Content is string str)
control = new Label() { Content = str };
else
control = (Control)w.Content!;
}
return control ?? CreateErrorControl("XAML加載失敗,返回了空控件。");
}
catch (Exception ex)
{
Console.WriteLine(ex);
return CreateErrorControl("發(fā)生異常:" + ex.Message);
}
}
//不可用
public static async Task<Control> RunAvaresResourceAsync(this Control topLevel, string fileName, string? code = null, string cls = "SampleView")
{
var assembly = topLevel.GetType().Assembly;
var assemblyName = assembly.GetName().Name;
var baseUri = new Uri($"avares://{assemblyName}");
try
{
// 獲取所有資源并匹配文件名
var allResources = AssetLoader.GetAssets(baseUri, null);
var resourceUri = allResources.FirstOrDefault(u =>
u.AbsolutePath.EndsWith(fileName, StringComparison.OrdinalIgnoreCase));
if (resourceUri == null)
{
var available = string.Join("\n", allResources.Select(u => u.AbsolutePath));
return CreateErrorControl($"找不到 {fileName}\n可用資源:\n{available}");
}
Stream resourceStream = AssetLoader.Open(resourceUri);
using var reader = new StreamReader(resourceStream);
var xaml = await reader.ReadToEndAsync();
return await topLevel.RunXamlAsync(xaml, code, cls);
}
catch (Exception ex)
{
return CreateErrorControl($"加載錯誤: {ex.Message}");
}
}
public static async Task<Control> RunResourceAsync(this Control topLevel, string fileName)
{
var assembly = topLevel.GetType().Assembly;
try
{
var path = $"{assembly.GetName().Name}.{fileName}";
// UI代碼必須在主線程中執(zhí)行
return await Dispatcher.UIThread.InvokeAsync(() =>
{
Control? control = topLevel.Create(path);
if (control is Window w)
{
if (w.Content is string str)
control = new Label() { Content = str };
else
control = (Control)w.Content!;
}
return control ?? CreateErrorControl("XAML加載失敗,返回了空控件。");
});
}
catch (Exception ex)
{
return await Dispatcher.UIThread.InvokeAsync(() =>
CreateErrorControl("發(fā)生異常:" + ex.Message));
}
}
private static Control CreateErrorControl(string message)
{
return new Border
{
Background = Brushes.MistyRose,
BorderBrush = Brushes.IndianRed,
BorderThickness = new Thickness(1),
Padding = new Thickness(1),
Child = new TextBlock
{
Text = message,
Foreground = Brushes.DarkRed,
FontWeight = FontWeight.Bold,
TextWrapping = TextWrapping.Wrap,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
},
};
}
//RoutedEventArgs擴展
public static object? OriginalSource(this RoutedEventArgs e)
{
return GetDesktopLifetime()?.MainWindow;
}
//命令行
public static string[]? GetCommandLine(this Control topLevel)
{
return GetDesktopLifetime()?.Args;
}
//讀取文件
public static async Task<string?> ReadAllTextAsync(this Control topLevel, string fileName)
{
var assembly = topLevel.GetType().Assembly;
var assemblyName = assembly.GetName().Name;
var baseUri = new Uri($"avares://{assemblyName}");
var allResources = AssetLoader.GetAssets(baseUri, null);
var resourceUri = allResources.FirstOrDefault(u =>
u.AbsolutePath.EndsWith(fileName, StringComparison.OrdinalIgnoreCase));
if (resourceUri == null)
return null;
Stream resourceStream = AssetLoader.Open(resourceUri);
using var reader = new StreamReader(resourceStream);
var content = await reader.ReadToEndAsync();
return content;
}
public static void SetZIndex(this Control topLevel, int index)
=> topLevel.SetValue(Panel.ZIndexProperty, index);
public static int GetZIndex(this Control topLevel)
=> topLevel.GetValue(Panel.ZIndexProperty);
public static T? GetService<T>()
{
var serviceType = typeof(T);
Console.WriteLine($"嘗試獲取服務(wù)類型:{serviceType.FullName}");
var locatorType = typeof(AvaloniaLocator);
PropertyInfo? currentProp = null;
Type? typeWalker = locatorType;
while (typeWalker != null)
{
currentProp = typeWalker.GetProperty("Current", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
if (currentProp != null)
{
Console.WriteLine("找到 AvaloniaLocator.Current 屬性");
break;
}
typeWalker = typeWalker.BaseType;
}
if (currentProp == null)
{
Console.WriteLine("未找到 AvaloniaLocator.Current 屬性");
return default;
}
var currentLocator = currentProp.GetValue(null);
if (currentLocator == null)
{
Console.WriteLine("AvaloniaLocator.Current 返回 null");
return default;
}
// 嘗試調(diào)用泛型方法 GetService<T>()
var methods = currentLocator.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.Where(m => m.Name == "GetService" && m.IsGenericMethod);
MethodInfo? genericMethod = methods.FirstOrDefault();
if (genericMethod != null)
{
try
{
var result = genericMethod.MakeGenericMethod(serviceType).Invoke(currentLocator, null);
if (result is T t)
{
Console.WriteLine($"通過泛型方法獲取服務(wù)成功:{serviceType.FullName}");
return t;
}
else
{
Console.WriteLine($"泛型方法返回結(jié)果為空或類型不匹配");
}
}
catch (Exception ex)
{
Console.WriteLine($"調(diào)用泛型方法 GetService<T>() 異常:{ex.Message}");
}
}
// 回退:GetService(Type)
MethodInfo? getServiceByType = currentLocator.GetType().GetMethod("GetService", new[] { typeof(Type) });
if (getServiceByType != null)
{
try
{
var service = getServiceByType.Invoke(currentLocator, new object[] { serviceType });
if (service is T t2)
{
Console.WriteLine($"通過 GetService(Type) 獲取服務(wù)成功:{serviceType.FullName}");
return t2;
}
else
{
Console.WriteLine($"GetService(Type) 返回結(jié)果為空或類型不匹配");
}
}
catch (Exception ex)
{
Console.WriteLine($"調(diào)用 GetService(Type) 異常:{ex.Message}");
}
}
// 回退實例或創(chuàng)建
try
{
var instanceProp = serviceType.GetProperty("Instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
if (instanceProp != null)
{
var instance = instanceProp.GetValue(null);
if (instance is T typedInstance)
{
Console.WriteLine($"通過靜態(tài) Instance 屬性獲取服務(wù)成功:{serviceType.FullName}");
return typedInstance;
}
else
{
Console.WriteLine($"Instance 屬性為空或類型不匹配");
}
}
if (!serviceType.IsAbstract)
{
var created = Activator.CreateInstance(serviceType);
if (created is T createdTyped)
{
Console.WriteLine($"通過 Activator.CreateInstance 創(chuàng)建服務(wù)成功:{serviceType.FullName}");
return createdTyped;
}
}
}
catch (Exception ex)
{
Console.WriteLine($"嘗試通過 Instance 或 Activator 創(chuàng)建服務(wù)失敗:{ex.Message}");
}
Console.WriteLine($"未能獲取服務(wù):{serviceType.FullName}");
return default;
}
//外接矩形
public static Rect GetMinBoundingMatrix(Visual visual, Visual relativeTo)
{
if (visual == null || relativeTo == null)
return new Rect();
var transform = visual.TransformToVisual(relativeTo);
if (transform is null)
return new Rect();
var bounds = visual.Bounds;
// 四個角點(本地坐標(biāo))
var points = new[]
{
new Point(0, 0),
new Point(bounds.Width, 0),
new Point(bounds.Width, bounds.Height),
new Point(0, bounds.Height)
};
// 應(yīng)用變換
var transformed = points.Select(p => transform.Value.Transform(p)).ToArray();
// 計算外接矩形
var minX = transformed.Min(p => p.X);
var maxX = transformed.Max(p => p.X);
var minY = transformed.Min(p => p.Y);
var maxY = transformed.Max(p => p.Y);
return new Rect(minX, minY, maxX - minX, maxY - minY);
}
//遞歸應(yīng)用矩陣變化
public static TransformedBounds? GetTransformedBounds(Visual visual, Visual relativeTo)
{
if (visual == null || relativeTo == null)
return null;
Rect clip = new Rect();
var transform = Matrix.Identity;
bool Visit(Visual v)
{
if (!v.IsVisible)
return false;
var bounds = new Rect(v.Bounds.Size);
if (v == relativeTo)
{
clip = bounds;
transform = Matrix.Identity;
return true;
}
var parent = v.GetVisualParent();
if (parent == null)
return false;
if (!Visit(parent))
return false;
// 計算當(dāng)前節(jié)點的渲染變換矩陣
var renderTransform = Matrix.Identity;
if (v.HasMirrorTransform)
{
var mirrorMatrix = new Matrix(-1.0, 0.0, 0.0, 1.0, v.Bounds.Width, 0);
renderTransform *= mirrorMatrix;
}
if (v.RenderTransform != null)
{
var origin = v.RenderTransformOrigin.ToPixels(bounds.Size);
var offset = Matrix.CreateTranslation(origin);
var finalTransform = (-offset) * v.RenderTransform.Value * offset;
renderTransform *= finalTransform;
}
// 疊加變換矩陣
transform = renderTransform *
Matrix.CreateTranslation(v.Bounds.Position) *
transform;
// 處理剪裁
if (v.ClipToBounds)
{
var globalBounds = bounds.TransformToAABB(transform);
var clipBounds = v.ClipToBounds ? globalBounds.Intersect(clip) : clip;
clip = clip.Intersect(clipBounds);
}
return true;
}
return Visit(visual) ? new TransformedBounds(new Rect(visual.Bounds.Size), clip, transform) : null;
}
//獲取所有command
public static List<ICommand> GetICommands(this Control topLevel)
{
var commands = new HashSet<ICommand>();
// 1. 先查 DataContext 中的所有 ICommand 屬性
if (topLevel.DataContext != null)
{
var props = topLevel.DataContext.GetType()
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => typeof(ICommand).IsAssignableFrom(p.PropertyType));
foreach (var prop in props)
{
if (prop.GetValue(topLevel.DataContext) is ICommand cmd)
commands.Add(cmd);
}
}
// 2. 再查控件自身的 ICommandSource.Command
if (topLevel is ICommandSource commandSource && commandSource.Command != null)
{
commands.Add(commandSource.Command);
}
// 3. 子控件遞歸
foreach (var child in topLevel.GetVisualChildren())
{
if (child is Control c)
commands.UnionWith(GetICommands(c));
}
return commands.ToList();
}
//在控件樹及其 DataContext 中按名稱查找所有 ICommand(允許省略 "Command" 后綴,去重)
public static List<ICommand> GetICommands(this Control topLevel, string name)
{
var commands = new HashSet<ICommand>();
string fullName = name.EndsWith("Command") ? name : name + "Command";
// 當(dāng)前控件:DataContext
if (topLevel.DataContext != null)
{
var prop = topLevel.DataContext.GetType().GetProperty(fullName, BindingFlags.Public | BindingFlags.Instance);
if (prop != null && typeof(ICommand).IsAssignableFrom(prop.PropertyType))
{
if (prop.GetValue(topLevel.DataContext) is ICommand cmd)
commands.Add(cmd);
}
}
// 當(dāng)前控件:ICommandSource
if (topLevel is ICommandSource commandSource && commandSource.Command != null)
{
if (topLevel.Name == name || topLevel.Name == fullName ||
commandSource.Command.GetType().Name == name || commandSource.Command.GetType().Name == fullName)
commands.Add(commandSource.Command);
}
// 遞歸子控件
foreach (var child in topLevel.GetVisualChildren())
{
if (child is Control c)
commands.UnionWith(GetICommands(c, name)); // 遞歸并去重
}
return commands.ToList();
}
public static ICommand? GetICommand(this Control topLevel, string name)
{
// 自動補 "Command" 后綴
string fullName = name.EndsWith("Command") ? name : name + "Command";
// 1. 當(dāng)前控件:檢查 DataContext 是否有這個命令
if (topLevel.DataContext != null)
{
var prop = topLevel.DataContext.GetType().GetProperty(fullName, BindingFlags.Public | BindingFlags.Instance);
if (prop != null && typeof(ICommand).IsAssignableFrom(prop.PropertyType))
{
return (ICommand?)prop.GetValue(topLevel.DataContext);
}
}
// 2. 當(dāng)前控件:如果是 ICommandSource 并且匹配名稱
if (topLevel is ICommandSource commandSource && commandSource.Command != null)
{
// 通過控件 Name 或 Command 類型名匹配
if (topLevel.Name == name || topLevel.Name == fullName ||
commandSource.Command.GetType().Name == name || commandSource.Command.GetType().Name == fullName)
return commandSource.Command;
}
// 3. 遞歸子控件
foreach (var child in topLevel.GetVisualChildren())
{
if (child is Control c)
{
return GetICommand(c, name);
}
}
return null;
}
public static IDisposable SetICommand(this Control topLevel, ICommand command, KeyGesture? keyGesture = null, AvaloniaProperty? targetProperty = null)
{
var disposables = new CompositeDisposable();
// 1. 監(jiān)聽 targetProperty 的值變化,執(zhí)行命令(先 CanExecute)
if (targetProperty != null)
{
var subscription = topLevel.GetObservable(targetProperty)
.Subscribe(value =>
{
if (command.CanExecute(value))
{
command.Execute(value);
}
});
disposables.Add(subscription);
}
// 2. 綁定命令
if (topLevel is ICommandSource commandSource)
{
commandSource.SetPropertyValue("Command", command);
}
else
// 3. 否則,嘗試添加 KeyBinding
{
var keyBinding = new KeyBinding
{
Command = command,
Gesture = keyGesture!
};
bool exists = topLevel.KeyBindings.Any(kb => kb.Command == command && kb.Gesture == keyGesture);
if (!exists)
{
topLevel.KeyBindings.Add(keyBinding);
}
else
{
Console.WriteLine("SetICommand:命令已存在于控件的 KeyBindings 中,未重復(fù)添加。");
}
}
return disposables;
}
//Rect轉(zhuǎn)Point[]
public static Point[] RectToPoints(this Rect rect)
{
return new Point[]
{
new Point(rect.Left, rect.Top), // 左上
new Point(rect.Right, rect.Top), // 右上
new Point(rect.Right, rect.Bottom), // 右下
new Point(rect.Left, rect.Bottom) // 左下
};
}
//動態(tài)修改MiterLimit
public static void SetMiterLimit(this Shape shape, double limit)
{
var oldPen = shape.GetFieldValue<IPen>("_strokePen");
// 創(chuàng)建新的 ImmutablePen
if (oldPen != null)
{
var newPen = new ImmutablePen(
oldPen.Brush as IImmutableBrush,
oldPen.Thickness,
oldPen.DashStyle as ImmutableDashStyle,
oldPen.LineCap,
oldPen.LineJoin,
limit
);
// 替換 _strokePen
shape.SetFieldValue("_strokePen", newPen);
shape.InvalidateVisual(); // 刷新顯示
}
}
//動態(tài)加載資源或者樣式
public static void Load(this Control topLevel, string path)
{
var uri = new Uri(path, UriKind.RelativeOrAbsolute);
// 嘗試作為 ResourceInclude 加入 Resources.MergedDictionaries
bool alreadyResource = topLevel.Resources.MergedDictionaries
.OfType<ResourceInclude>()
.Any(r => r.Source == uri);
if (!alreadyResource)
{
var include = new ResourceInclude(uri)
{
Source = uri
};
try
{
topLevel.Resources.MergedDictionaries.Add(include);
}
catch
{
// 不是 ResourceDictionary,嘗試作為 Styles
bool alreadyStyle = topLevel.Styles
.OfType<StyleInclude>()
.Any(s => s.Source == uri);
if (!alreadyStyle)
{
var styleInclude = new StyleInclude(uri)
{
Source = uri
};
topLevel.Styles.Add(styleInclude);
}
}
}
}
public static void LayoutTransform(this Control topLevel)
{
if (topLevel == null)
{
Console.WriteLine($"{nameof(topLevel)}不能為空");
return;
}
// 找到父容器
var parent = topLevel.Parent as Panel;
if (parent == null)
return;
// 找到控件在父容器中的位置
var index = parent.Children.IndexOf(topLevel);
if (index < 0)
return;
// 從父容器移除原控件
parent.Children.RemoveAt(index);
parent.Children.Insert(index, new LayoutTransformControl
{
Child = topLevel
});
}
}
}
Reflection.cs代碼
using System;
using System.Linq;
using System.Reflection;
namespace Shares
{
public static class Reflection
{
public static T? GetPropertyValue<T>(this object obj, string propertyName)
{
if (obj == null)
{
Console.WriteLine("GetPropertyValue:傳入對象為 null");
return default;
}
Type? type = obj.GetType();
PropertyInfo? prop = null;
while (type != null)
{
prop = type.GetProperty(propertyName, BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (prop != null)
break;
type = type.BaseType;
}
if (prop == null)
{
Console.WriteLine($"未找到屬性 '{propertyName}',類型鏈中沒有該屬性");
return default;
}
var value = prop.GetValue(obj);
if (value is T t)
return t;
try
{
return (T?)Convert.ChangeType(value, typeof(T));
}
catch
{
Console.WriteLine($"屬性 '{propertyName}' 類型不匹配。期望 {typeof(T).FullName},實際 {value?.GetType().FullName ?? "null"}");
return default;
}
}
public static bool SetPropertyValue<T>(this object obj, string propertyName, T value)
{
if (obj == null)
{
Console.WriteLine("SetPropertyValue:目標(biāo)對象為 null");
return false;
}
if (string.IsNullOrEmpty(propertyName))
{
Console.WriteLine("SetPropertyValue:屬性名為空");
return false;
}
Type? type = obj.GetType();
PropertyInfo? property = null;
while (type != null)
{
property = type.GetProperty(propertyName, BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (property != null)
break;
type = type.BaseType;
}
if (property == null)
{
Console.WriteLine($"SetPropertyValue:未找到屬性 '{propertyName}',類型鏈中沒有該屬性");
return false;
}
if (!property.CanWrite)
{
Console.WriteLine($"SetPropertyValue:屬性 '{propertyName}' 不可寫");
return false;
}
try
{
property.SetValue(obj, value);
return true;
}
catch (Exception ex)
{
Console.WriteLine($"SetPropertyValue:設(shè)置屬性 '{propertyName}' 失敗,異常:{ex}");
return false;
}
}
public static T? GetFieldValue<T>(this object obj, string fieldName)
{
if (obj == null)
{
Console.WriteLine("GetFieldValue:傳入對象為 null");
return default;
}
Type? type = obj.GetType();
FieldInfo? field = null;
while (type != null)
{
field = type.GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static);
if (field != null)
break;
type = type.BaseType;
}
if (field == null)
{
Console.WriteLine($"未找到字段 '{fieldName}',類型鏈中沒有該字段");
return default;
}
var value = field.GetValue(obj);
if (value is T t)
return t;
try
{
return (T?)Convert.ChangeType(value, typeof(T));
}
catch
{
Console.WriteLine($"字段 '{fieldName}' 類型不匹配。期望 {typeof(T).FullName},實際 {value?.GetType().FullName ?? "null"}");
return default;
}
}
public static bool SetFieldValue<T>(this object obj, string fieldName, T value)
{
if (obj == null)
{
Console.WriteLine("SetFieldValue:目標(biāo)對象為 null");
return false;
}
if (string.IsNullOrEmpty(fieldName))
{
Console.WriteLine("SetFieldValue:字段名為空");
return false;
}
Type? type = obj.GetType();
FieldInfo? field = null;
while (type != null)
{
field = type.GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static);
if (field != null)
break;
type = type.BaseType;
}
if (field == null)
{
Console.WriteLine($"SetFieldValue:未找到字段 '{fieldName}',類型鏈中沒有該字段");
return false;
}
try
{
field.SetValue(obj, value);
return true;
}
catch (Exception ex)
{
Console.WriteLine($"SetFieldValue:設(shè)置字段 '{fieldName}' 失敗,異常:{ex}");
return false;
}
}
public static object? InvokeMethod(this object obj, string methodName, params object?[]? parameters)
{
if (obj == null)
{
Console.WriteLine("InvokeMethod:目標(biāo)對象為 null");
return null;
}
if (string.IsNullOrWhiteSpace(methodName))
{
Console.WriteLine("InvokeMethod:方法名為空");
return null;
}
parameters ??= Array.Empty<object?>();
Type? type = obj.GetType();
MethodInfo? method = null;
while (type != null)
{
var methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
.Where(m => m.Name == methodName);
foreach (var m in methods)
{
var ps = m.GetParameters();
if (ps.Length == parameters.Length)
{
method = m;
break;
}
}
if (method != null)
break;
type = type.BaseType;
}
if (method == null)
{
Console.WriteLine($"InvokeMethod:未找到方法 '{methodName}',參數(shù)個數(shù):{parameters.Length}");
return null;
}
try
{
var result = method.Invoke(obj, parameters);
return method.ReturnType == typeof(void) ? null : result;
}
catch (Exception ex)
{
Console.WriteLine($"InvokeMethod:調(diào)用 '{methodName}' 異常:{ex.Message}");
return null;
}
}
public static T? InvokeMethod<T>(this object obj, string methodName, params object?[]? parameters)
{
var result = obj.InvokeMethod(methodName, parameters);
return result is T t ? t : default;
}
}
}
CompilerService.cs代碼
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Text;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.Loader;
using System.Text;
using System.Threading.Tasks;
namespace Shares.Avalonia
{
public static class CompilerService
{
public static bool IsBrowser()
{
return RuntimeInformation.IsOSPlatform(OSPlatform.Create("BROWSER"));
}
private static PortableExecutableReference[]? s_references;
public static string? BaseUri { get; set; }
private static async Task LoadReferences()
{
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
if (IsBrowser())
{
if (BaseUri is null)
{
return;
}
var appDomainReferences = new List<PortableExecutableReference>();
var client = new HttpClient
{
BaseAddress = new Uri(BaseUri)
};
Console.WriteLine($"Loading references BaseUri: {BaseUri}");
foreach (var reference in assemblies.Where(x => !x.IsDynamic))
{
try
{
var name = reference.GetName().Name;
var requestUri = $"{BaseUri}managed/{name}.dll";
Console.WriteLine($"Loading reference requestUri: {requestUri}, FullName: {reference.FullName}");
var stream = await client.GetStreamAsync(requestUri);
appDomainReferences.Add(MetadataReference.CreateFromStream(stream));
}
catch (Exception exception)
{
Console.WriteLine(exception);
}
}
s_references = appDomainReferences.ToArray();
}
else
{
var appDomainReferences = new List<PortableExecutableReference>();
foreach (var reference in assemblies.Where(x => !x.IsDynamic && !string.IsNullOrWhiteSpace(x.Location)))
{
appDomainReferences.Add(MetadataReference.CreateFromFile(reference.Location));
}
s_references = appDomainReferences.ToArray();
}
}
public static async Task<(Assembly? Assembly, AssemblyLoadContext? Context)> GetScriptAssembly(string code)
{
if (s_references is null)
{
await LoadReferences();
}
var stringText = SourceText.From(code, Encoding.UTF8);
var parseOptions = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.Latest);
var parsedSyntaxTree = SyntaxFactory.ParseSyntaxTree(stringText, parseOptions);
var compilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary).WithOptimizationLevel(OptimizationLevel.Release);
var compilation = CSharpCompilation.Create(Path.GetRandomFileName(), new[] { parsedSyntaxTree }, s_references, compilationOptions);
using var ms = new MemoryStream();
var result = compilation.Emit(ms);
var errors = result.Diagnostics.Where(x => x.Severity == DiagnosticSeverity.Error);
if (!result.Success)
{
foreach (var error in errors)
{
Console.WriteLine(error);
}
return (null, null);
}
ms.Seek(0, SeekOrigin.Begin);
var context = new AssemblyLoadContext(name: Path.GetRandomFileName(), isCollectible: true);
var assembly = context.LoadFromStream(ms);
return (assembly, context);
}
public static async Task<(Assembly? Assembly, AssemblyLoadContext? Context)> GetScriptAssembly(Dictionary<string, string> sourceFiles)
{
if (s_references is null)
await LoadReferences();
var syntaxTrees = new List<SyntaxTree>();
var parseOptions = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.Latest);
foreach (var kv in sourceFiles)
{
var sourceText = SourceText.From(kv.Value, Encoding.UTF8);
var syntaxTree = SyntaxFactory.ParseSyntaxTree(sourceText, parseOptions, path: kv.Key);
syntaxTrees.Add(syntaxTree);
}
var compilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
.WithOptimizationLevel(OptimizationLevel.Release);
var compilation = CSharpCompilation.Create(
assemblyName: Path.GetRandomFileName(),
syntaxTrees: syntaxTrees,
references: s_references,
options: compilationOptions
);
using var ms = new MemoryStream();
var result = compilation.Emit(ms);
if (!result.Success)
{
foreach (var diag in result.Diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error))
Console.WriteLine(diag);
return (null, null);
}
ms.Seek(0, SeekOrigin.Begin);
var context = new AssemblyLoadContext(Path.GetRandomFileName(), isCollectible: true);
var assembly = context.LoadFromStream(ms);
return (assembly, context);
}
}
}
浙公網(wǎng)安備 33010602011771號