diff --git a/IPA.Loader/IPA.Loader.csproj b/IPA.Loader/IPA.Loader.csproj index 1687171b..9540d98c 100644 --- a/IPA.Loader/IPA.Loader.csproj +++ b/IPA.Loader/IPA.Loader.csproj @@ -4,7 +4,7 @@ Debug Net4 - true + true {5AD344F0-01A0-4CA8-92E5-9D095737744D} Library Properties diff --git a/IPA.Loader/Loader/PluginExecutor.cs b/IPA.Loader/Loader/PluginExecutor.cs index c3b74f83..f217f1cd 100644 --- a/IPA.Loader/Loader/PluginExecutor.cs +++ b/IPA.Loader/Loader/PluginExecutor.cs @@ -8,6 +8,8 @@ using System.Linq.Expressions; #if NET4 using Task = System.Threading.Tasks.Task; using TaskEx = System.Threading.Tasks.Task; +using Expression = System.Linq.Expressions.Expression; +using ExpressionEx = System.Linq.Expressions.Expression; #endif #if NET3 using System.Threading.Tasks; @@ -19,6 +21,7 @@ using Directory = Net3_Proxy.Directory; namespace IPA.Loader { + // NOTE: TaskEx.WhenAll() (Task.WhenAll() in .NET 4) returns CompletedTask if it has no arguments, which we need for .NET 3 internal class PluginExecutor { public PluginMetadata Metadata { get; } @@ -29,7 +32,7 @@ namespace IPA.Loader { CreatePlugin = m => null; LifecycleEnable = o => { }; - LifecycleDisable = o => TaskEx.CompletedTask; + LifecycleDisable = o => TaskEx.WhenAll(); } else PrepareDelegates(); @@ -95,13 +98,13 @@ namespace IPA.Loader // TODO: how do I make this work for .NET 3? FEC.LightExpression but hacked to work on .NET 3? var metaParam = Expression.Parameter(typeof(PluginMetadata), "meta"); - var objVar = Expression.Variable(type, "objVar"); - var persistVar = Expression.Variable(typeof(object), "persistVar"); + var objVar = ExpressionEx.Variable(type, "objVar"); + var persistVar = ExpressionEx.Variable(typeof(object), "persistVar"); var createExpr = Expression.Lambda>( - Expression.Block(new[] { objVar, persistVar }, + ExpressionEx.Block(new[] { objVar, persistVar }, initMethods .Select(m => PluginInitInjector.InjectedCallExpr(m.GetParameters(), metaParam, persistVar, es => Expression.Call(objVar, m, es))) - .Prepend(Expression.Assign(objVar, + .Prepend(ExpressionEx.Assign(objVar, usingDefaultCtor ? Expression.New(ctor) : PluginInitInjector.InjectedCallExpr(ctor.GetParameters(), metaParam, persistVar, es => Expression.New(ctor, es)))) @@ -133,12 +136,12 @@ namespace IPA.Loader } var objParam = Expression.Parameter(typeof(object), "obj"); - var instVar = Expression.Variable(type, "inst"); + var instVar = ExpressionEx.Variable(type, "inst"); var createExpr = Expression.Lambda>( - Expression.Block(new[] { instVar }, + ExpressionEx.Block(new[] { instVar }, enableMethods - .Select(m => Expression.Call(instVar, m)) - .Prepend(Expression.Assign(instVar, Expression.Convert(objParam, type)))), + .Select(m => (Expression)Expression.Call(instVar, m)) + .Prepend(ExpressionEx.Assign(instVar, Expression.Convert(objParam, type)))), objParam); return createExpr.Compile(); } @@ -152,7 +155,7 @@ namespace IPA.Loader if (disableMethods.Length == 0) { Logger.loader.Notice($"Plugin {name} has no methods marked [OnExit] or [OnDisable]. Is this intentional?"); - return o => Task.CompletedTask; + return o => TaskEx.WhenAll(); } var taskMethods = new List(); @@ -175,24 +178,24 @@ namespace IPA.Loader nonTaskMethods.Add(m); } - Expression> completedTaskDel = () => TaskEx.CompletedTask; + Expression> completedTaskDel = () => TaskEx.WhenAll(); var getCompletedTask = completedTaskDel.Body; var taskWhenAll = typeof(TaskEx).GetMethod(nameof(TaskEx.WhenAll), new[] { typeof(Task[]) }); var objParam = Expression.Parameter(typeof(object), "obj"); - var instVar = Expression.Variable(type, "inst"); + var instVar = ExpressionEx.Variable(type, "inst"); var createExpr = Expression.Lambda>( - Expression.Block(new[] { instVar }, + ExpressionEx.Block(new[] { instVar }, nonTaskMethods - .Select(m => Expression.Call(instVar, m)) - .Prepend(Expression.Assign(instVar, Expression.Convert(objParam, type))) + .Select(m => (Expression)Expression.Call(instVar, m)) + .Prepend(ExpressionEx.Assign(instVar, Expression.Convert(objParam, type))) .Append( taskMethods.Count == 0 ? getCompletedTask : Expression.Call(taskWhenAll, Expression.NewArrayInit(typeof(Task), taskMethods.Select(m => - Expression.Convert(Expression.Call(instVar, m), typeof(Task))))))), + (Expression)Expression.Convert(Expression.Call(instVar, m), typeof(Task))))))), objParam); return createExpr.Compile(); } diff --git a/IPA.Loader/Loader/PluginInitInjector.cs b/IPA.Loader/Loader/PluginInitInjector.cs index 9d9adc27..c4d085ab 100644 --- a/IPA.Loader/Loader/PluginInitInjector.cs +++ b/IPA.Loader/Loader/PluginInitInjector.cs @@ -6,6 +6,10 @@ using IPA.Config; using IPA.Logging; using IPA.Utilities; using System.Linq.Expressions; +#if NET4 +using Expression = System.Linq.Expressions.Expression; +using ExpressionEx = System.Linq.Expressions.Expression; +#endif #if NET3 using Net3_Proxy; #endif @@ -124,14 +128,14 @@ namespace IPA.Loader } private static readonly MethodInfo InjectMethod = typeof(PluginInitInjector).GetMethod(nameof(Inject), BindingFlags.NonPublic | BindingFlags.Static); - internal static Expression InjectedCallExpr(ParameterInfo[] initParams, Expression meta, ParameterExpression persistVar, Func, Expression> exprGen) + internal static Expression InjectedCallExpr(ParameterInfo[] initParams, Expression meta, Expression persistVar, Func, Expression> exprGen) { - var arr = Expression.Variable(typeof(object[]), "initArr"); - return Expression.Block(new[] { arr }, - Expression.Assign(arr, Expression.Call(InjectMethod, Expression.Constant(initParams), meta, persistVar)), + var arr = ExpressionEx.Variable(typeof(object[]), "initArr"); + return ExpressionEx.Block(new[] { arr }, + ExpressionEx.Assign(arr, Expression.Call(InjectMethod, Expression.Constant(initParams), meta, persistVar)), exprGen(initParams .Select(p => p.ParameterType) - .Select((t, i) => Expression.Convert( + .Select((t, i) => (Expression)Expression.Convert( Expression.ArrayIndex(arr, Expression.Constant(i)), t)))); } diff --git a/Net3-Proxy/ExpressionEx.cs b/Net3-Proxy/ExpressionEx.cs new file mode 100644 index 00000000..eb2ebaa7 --- /dev/null +++ b/Net3-Proxy/ExpressionEx.cs @@ -0,0 +1,386 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Linq.Expressions; +using System.Reflection; + +namespace Net3_Proxy +{ + public static class ExpressionEx + { + internal static ExpressionType ExprT(this ExpressionTypeEx ex) + => (ExpressionType)ex; + internal static ExpressionTypeEx ExprTEx(this ExpressionType ex) + => (ExpressionTypeEx)ex; + + private static void Validate(Type type, bool allowByRef) + { + if (type == null) + throw new ArgumentNullException(nameof(type)); + TypeUtils.ValidateType(type, nameof(type), allowByRef, false); + if (type == typeof(void)) + throw new ArgumentException("Argument cannot be of type void", nameof(type)); + } + + private static void RequiresCanWrite(Expression expression, string paramName) + { + if (expression == null) + { + throw new ArgumentNullException(paramName); + } + ExpressionType nodeType = expression.NodeType; + if (nodeType != ExpressionType.MemberAccess) + { + if (nodeType == ExpressionType.Parameter) + return; + } + else + { + MemberInfo member = ((MemberExpression)expression).Member; + if (member is PropertyInfo propertyInfo) + { + if (propertyInfo.CanWrite) + { + return; + } + } + else + { + FieldInfo fieldInfo = (FieldInfo)member; + if (!fieldInfo.IsInitOnly && !fieldInfo.IsLiteral) + { + return; + } + } + } + throw new ArgumentException("Expression must be writeable", paramName); + } + + public static void RequiresCanRead(Expression expression, string paramName, int idx = -1) + { + if (expression == null) + throw new ArgumentNullException(TypeUtils.GetParamName(paramName, idx)); + ExpressionType nodeType = expression.NodeType; + if (nodeType == ExpressionType.MemberAccess) + { + if (((MemberExpression)expression).Member is PropertyInfo propertyInfo && !propertyInfo.CanRead) + { + throw new ArgumentException("Expression must be readable", TypeUtils.GetParamName(paramName, idx)); + } + } + } + + public static VariableExpression Variable(Type type, string name = null) + { + Validate(type, false); + return new VariableExpression(type, name); + } + + public static AssignExpression Assign(Expression left, Expression right) + { + RequiresCanWrite(left, nameof(left)); + RequiresCanRead(right, nameof(right)); + TypeUtils.ValidateType(left.Type, nameof(left), true, true); + TypeUtils.ValidateType(right.Type, nameof(right), true, true); + if (!TypeUtils.AreReferenceAssignable(left.Type, right.Type)) + throw new ArgumentException($"Expression of type '{left.Type}' cannot be used for assignment to type '{right.Type}'"); + if (left.NodeType.ExprTEx() != ExpressionTypeEx.Variable) + throw new NotSupportedException("Non-variable left hand operands to assignment is currently not supported"); + return new AssignExpression(left, right); + } + + public static Expression Block(IEnumerable vars, params Expression[] body) => Block(vars, body); + public static Expression Block(IEnumerable vars, IEnumerable body) + { // this is probably terrible performance-wise when executing, but i'm not giving up BlockExpression damnit! + var varSet = new HashSet(vars); + var bodyArr = body.ToArray(); + var finalExprList = new Stack<(List list, BlockParseStackInfo info)>(); + var remaining = bodyArr.Length; + + var varParams = new Dictionary(varSet.Count); + + finalExprList.Push((new List(remaining), default(BlockParseStackInfo))); + + while (remaining > 0) + { + var (list, info) = finalExprList.Pop(); + var targetExpr = bodyArr[bodyArr.Length - remaining--]; + + if (targetExpr.NodeType.ExprTEx() == ExpressionTypeEx.Assign) + { + var assign = (AssignExpression)targetExpr; + var left = (VariableExpression)assign.Left; + var right = assign.Right; + + var param = Expression.Parameter(left.Type, left.Name); + + finalExprList.Push((list, info)); + finalExprList.Push((new List(remaining), new BlockParseStackInfo + { + ValueExpr = BlockVisitReplaceVariables(right, varParams), Param = param + })); + + varParams.Add(left, param); + continue; + } + + list.Add(BlockVisitReplaceVariables(targetExpr, varParams)); + finalExprList.Push((list, info)); + } + + var funcType = typeof(Func<>); + Expression topExpr = null; + + while (finalExprList.Count > 0) + { + var (list, info) = finalExprList.Pop(); + // there is an optimization opportunity for consecutive assignments, but it needs to make sure they don't depend on each other + if (topExpr != null) list.Add(topExpr); + + + Expression last = null; + Type lastType = null; + var rest = new List>(list.Count); + + for (int i = 0; i < list.Count; i++) + { + var expr = list[i]; + if (i + 1 == list.Count) + { + var ty = expr.Type; + if (ty == typeof(void)) + rest.Add(Expression.Lambda(expr)); + else + { + lastType = ty; + var func = funcType.MakeGenericType(ty); + last = Expression.Lambda(func, expr); + } + } + else + rest.Add(Expression.Lambda(expr)); + } + + Expression topBody; + if (lastType != null) + { + var execSeq = ExecuteSequenceTyped.MakeGenericMethod(lastType); + topBody = Expression.Call(null, execSeq, last, Expression.NewArrayInit(typeof(Action), rest.Cast())); + } + else + topBody = Expression.Call(null, ExecuteSequenceVoid, Expression.NewArrayInit(typeof(Action), rest.Cast())); + + if (info.Param != null && info.ValueExpr != null) + topExpr = Expression.Invoke(Expression.Lambda(topBody, info.Param), info.ValueExpr); + else + topExpr = topBody; + } + + return topExpr; + } + + /* + * { + * Console.WriteLine("ho"); + * int i = 3; + * Console.WriteLine(i); + * i // return last + * } + * + * ExecuteSequence( + * () => (i => + * ExecuteSequence( + * () => i, + * () => Console.WriteLine(i) + * ) + * )(3), + * () => Console.WriteLine("ho") + * ) + */ + + private struct BlockParseStackInfo + { + public Expression ValueExpr; + public ParameterExpression Param; + } + + private static Expression BlockVisitReplaceVariables(Expression expr, Dictionary mapping) + { + var binary = expr as BinaryExpression; + var unary = expr as UnaryExpression; + switch (expr.NodeType) + { + case ExpressionType.Add: + return Expression.Add(BlockVisitReplaceVariables(binary.Left, mapping), BlockVisitReplaceVariables(binary.Right, mapping)); + case ExpressionType.AddChecked: + return Expression.AddChecked(BlockVisitReplaceVariables(binary.Left, mapping), BlockVisitReplaceVariables(binary.Right, mapping)); + case ExpressionType.And: + return Expression.And(BlockVisitReplaceVariables(binary.Left, mapping), BlockVisitReplaceVariables(binary.Right, mapping)); + case ExpressionType.AndAlso: + return Expression.AndAlso(BlockVisitReplaceVariables(binary.Left, mapping), BlockVisitReplaceVariables(binary.Right, mapping)); + case ExpressionType.ArrayIndex: + return Expression.ArrayIndex(BlockVisitReplaceVariables(binary.Left, mapping), BlockVisitReplaceVariables(binary.Right, mapping)); + case ExpressionType.Coalesce: + return Expression.Coalesce(BlockVisitReplaceVariables(binary.Left, mapping), BlockVisitReplaceVariables(binary.Right, mapping)); + case ExpressionType.Divide: + return Expression.Divide(BlockVisitReplaceVariables(binary.Left, mapping), BlockVisitReplaceVariables(binary.Right, mapping)); + case ExpressionType.Equal: + return Expression.Equal(BlockVisitReplaceVariables(binary.Left, mapping), BlockVisitReplaceVariables(binary.Right, mapping)); + case ExpressionType.ExclusiveOr: + return Expression.ExclusiveOr(BlockVisitReplaceVariables(binary.Left, mapping), BlockVisitReplaceVariables(binary.Right, mapping)); + case ExpressionType.GreaterThan: + return Expression.GreaterThan(BlockVisitReplaceVariables(binary.Left, mapping), BlockVisitReplaceVariables(binary.Right, mapping)); + case ExpressionType.GreaterThanOrEqual: + return Expression.GreaterThanOrEqual(BlockVisitReplaceVariables(binary.Left, mapping), BlockVisitReplaceVariables(binary.Right, mapping)); + case ExpressionType.LeftShift: + return Expression.LeftShift(BlockVisitReplaceVariables(binary.Left, mapping), BlockVisitReplaceVariables(binary.Right, mapping)); + case ExpressionType.LessThan: + return Expression.LessThan(BlockVisitReplaceVariables(binary.Left, mapping), BlockVisitReplaceVariables(binary.Right, mapping)); + case ExpressionType.LessThanOrEqual: + return Expression.LessThanOrEqual(BlockVisitReplaceVariables(binary.Left, mapping), BlockVisitReplaceVariables(binary.Right, mapping)); + case ExpressionType.Modulo: + return Expression.Modulo(BlockVisitReplaceVariables(binary.Left, mapping), BlockVisitReplaceVariables(binary.Right, mapping)); + case ExpressionType.Multiply: + return Expression.Multiply(BlockVisitReplaceVariables(binary.Left, mapping), BlockVisitReplaceVariables(binary.Right, mapping)); + case ExpressionType.MultiplyChecked: + return Expression.MultiplyChecked(BlockVisitReplaceVariables(binary.Left, mapping), BlockVisitReplaceVariables(binary.Right, mapping)); + case ExpressionType.NotEqual: + return Expression.NotEqual(BlockVisitReplaceVariables(binary.Left, mapping), BlockVisitReplaceVariables(binary.Right, mapping)); + case ExpressionType.Or: + return Expression.Or(BlockVisitReplaceVariables(binary.Left, mapping), BlockVisitReplaceVariables(binary.Right, mapping)); + case ExpressionType.OrElse: + return Expression.OrElse(BlockVisitReplaceVariables(binary.Left, mapping), BlockVisitReplaceVariables(binary.Right, mapping)); + case ExpressionType.Power: + return Expression.Power(BlockVisitReplaceVariables(binary.Left, mapping), BlockVisitReplaceVariables(binary.Right, mapping)); + case ExpressionType.RightShift: + return Expression.RightShift(BlockVisitReplaceVariables(binary.Left, mapping), BlockVisitReplaceVariables(binary.Right, mapping)); + case ExpressionType.Subtract: + return Expression.Subtract(BlockVisitReplaceVariables(binary.Left, mapping), BlockVisitReplaceVariables(binary.Right, mapping)); + case ExpressionType.SubtractChecked: + return Expression.SubtractChecked(BlockVisitReplaceVariables(binary.Left, mapping), BlockVisitReplaceVariables(binary.Right, mapping)); + case ExpressionType.ArrayLength: + return Expression.ArrayLength(BlockVisitReplaceVariables(unary.Operand, mapping)); + case ExpressionType.Negate: + return Expression.Negate(BlockVisitReplaceVariables(unary.Operand, mapping)); + case ExpressionType.UnaryPlus: + return Expression.UnaryPlus(BlockVisitReplaceVariables(unary.Operand, mapping)); + case ExpressionType.NegateChecked: + return Expression.NegateChecked(BlockVisitReplaceVariables(unary.Operand, mapping)); + case ExpressionType.Not: + return Expression.Not(BlockVisitReplaceVariables(unary.Operand, mapping)); + case ExpressionType.TypeAs: + return Expression.TypeAs(BlockVisitReplaceVariables(unary.Operand, mapping), unary.Type); + case ExpressionType.Call: + var callExpr = expr as MethodCallExpression; + return Expression.Call(BlockVisitReplaceVariables(callExpr.Object, mapping), + callExpr.Method, callExpr.Arguments.Select(a => BlockVisitReplaceVariables(a, mapping))); + case ExpressionType.Conditional: + var condExpr = expr as ConditionalExpression; + return Expression.Condition(BlockVisitReplaceVariables(condExpr.Test, mapping), + BlockVisitReplaceVariables(condExpr.IfTrue, mapping), BlockVisitReplaceVariables(condExpr.IfFalse, mapping)); + case ExpressionType.Constant: return expr; // constants should be unchanged + case ExpressionType.Convert: + return Expression.Convert(BlockVisitReplaceVariables(unary.Operand, mapping), unary.Type, unary.Method); + case ExpressionType.ConvertChecked: + return Expression.ConvertChecked(BlockVisitReplaceVariables(unary.Operand, mapping), unary.Type, unary.Method); + case ExpressionType.Invoke: + var invokeExpr = expr as InvocationExpression; + return Expression.Invoke(BlockVisitReplaceVariables(invokeExpr.Expression, mapping), + invokeExpr.Arguments.Select(e => BlockVisitReplaceVariables(e, mapping))); + case ExpressionType.Lambda: + var lambdaExpr = expr as LambdaExpression; + return Expression.Lambda(lambdaExpr.Type, BlockVisitReplaceVariables(lambdaExpr.Body, mapping), lambdaExpr.Parameters); + case ExpressionType.ListInit: + var listInitExpr = expr as ListInitExpression; + return Expression.ListInit((NewExpression)BlockVisitReplaceVariables(listInitExpr.NewExpression, mapping), + listInitExpr.Initializers.Select(i => i.AddMethod).First(), + listInitExpr.Initializers.SelectMany(i => i.Arguments) + .Select(e => BlockVisitReplaceVariables(e, mapping))); + case ExpressionType.MemberAccess: + var memberExpr = expr as MemberExpression; + return Expression.MakeMemberAccess(BlockVisitReplaceVariables(memberExpr.Expression, mapping), memberExpr.Member); + case ExpressionType.MemberInit: + var memberInitExpr = expr as MemberInitExpression; + return Expression.MemberInit((NewExpression)BlockVisitReplaceVariables(memberInitExpr.NewExpression, mapping), + memberInitExpr.Bindings); + case ExpressionType.New: + var newExpr = expr as NewExpression; + return Expression.New(newExpr.Constructor, + newExpr.Arguments.Select(e => BlockVisitReplaceVariables(e, mapping)), + newExpr.Members); + case ExpressionType.NewArrayInit: + var newArrayInitExpr = expr as NewArrayExpression; + return Expression.NewArrayInit(newArrayInitExpr.Type, + newArrayInitExpr.Expressions.Select(e => BlockVisitReplaceVariables(e, mapping))); + case ExpressionType.NewArrayBounds: + var newArrayBoundsExpr = expr as NewArrayExpression; + return Expression.NewArrayBounds(newArrayBoundsExpr.Type, + newArrayBoundsExpr.Expressions.Select(e => BlockVisitReplaceVariables(e, mapping))); + case ExpressionType.Parameter: return expr; // like constant + case ExpressionType.Quote: + return Expression.Quote(BlockVisitReplaceVariables(unary.Operand, mapping)); + case ExpressionType.TypeIs: + var typeIsExpr = expr as TypeBinaryExpression; + return Expression.TypeIs(BlockVisitReplaceVariables(typeIsExpr.Expression, mapping), typeIsExpr.TypeOperand); + default: + switch (expr.NodeType.ExprTEx()) + { + case ExpressionTypeEx.Variable: + var varExpr = expr as VariableExpression; + if (mapping.TryGetValue(varExpr, out var paramExpr)) + return paramExpr; + else + return varExpr; // not in scope in the current context, might be later + case ExpressionTypeEx.Assign: + throw new InvalidOperationException("Assign expression must appear directly inside a block expression"); + default: + throw new ArgumentException($"Unhandled expression type '{expr.NodeType}'"); + } + } + } + + private static readonly MethodInfo ExecuteSequenceVoid = typeof(ExpressionEx).GetMethod(nameof(ExecuteSequence), + BindingFlags.Static | BindingFlags.NonPublic, null, new[] { typeof(Action[]) }, null); + private static void ExecuteSequence(params Action[] exec) + { + foreach (var act in exec) act(); + } + private static readonly MethodInfo ExecuteSequenceTyped = typeof(ExpressionEx).GetMethod(nameof(ExecuteSequence), + BindingFlags.Static | BindingFlags.NonPublic, null, new[] { typeof(Func<>), typeof(Action[]) }, null); + private static T ExecuteSequence(Func last, params Action[] first) + { + ExecuteSequence(first); + return last(); + } + } + + internal enum ExpressionTypeEx + { + Variable = 46, + Assign = 47 + } + + public class VariableExpression : Expression, IEquatable + { + public string Name { get; } + + internal VariableExpression(Type varType, string name) : base(ExpressionTypeEx.Variable.ExprT(), varType) + => Name = name; + + public bool Equals(VariableExpression other) + => Name == other.Name && Type == other.Type; + } + + public class AssignExpression : Expression + { + public Expression Left { get; } + public Expression Right { get; } + + internal AssignExpression(Expression left, Expression right) : base(ExpressionTypeEx.Assign.ExprT(), left.Type) + { + Left = left; + Right = right; + } + } +} diff --git a/Net3-Proxy/Extensions.cs b/Net3-Proxy/Extensions.cs index 56f92a03..89b75da0 100644 --- a/Net3-Proxy/Extensions.cs +++ b/Net3-Proxy/Extensions.cs @@ -15,9 +15,15 @@ namespace Net3_Proxy { public static T GetCustomAttribute(this ParameterInfo element) where T : Attribute => (T)GetCustomAttribute(element, typeof(T)); + public static T GetCustomAttribute(this MethodInfo element) where T : Attribute + => (T)GetCustomAttribute(element, typeof(T)); + public static T GetCustomAttribute(this ConstructorInfo element) where T : Attribute + => (T)GetCustomAttribute(element, typeof(T)); public static Attribute GetCustomAttribute(this MemberInfo element, Type attributeType) => Attribute.GetCustomAttribute(element, attributeType); + public static Attribute GetCustomAttribute(this ConstructorInfo element, Type attributeType) + => Attribute.GetCustomAttribute(element, attributeType); public static Attribute GetCustomAttribute(this ParameterInfo element, Type attributeType) => Attribute.GetCustomAttribute(element, attributeType); diff --git a/Net3-Proxy/Net3-Proxy.csproj b/Net3-Proxy/Net3-Proxy.csproj index ee06d43e..67f6e4c5 100644 --- a/Net3-Proxy/Net3-Proxy.csproj +++ b/Net3-Proxy/Net3-Proxy.csproj @@ -1,59 +1,64 @@ - - - - - Debug - AnyCPU - {642F52DA-90F9-40E3-8784-6964F36752FB} - Library - Properties - Net3_Proxy - Net3-Proxy - v3.5 - 512 - true - - - true - portable - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - portable - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - - - - - - - - - - - - - 0.3.1 - - - + + + + + Debug + AnyCPU + {642F52DA-90F9-40E3-8784-6964F36752FB} + Library + Properties + Net3_Proxy + Net3-Proxy + v3.5 + 512 + true + + + true + portable + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + portable + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + 0.3.1 + + + 1.0.1 + + + \ No newline at end of file diff --git a/Net3-Proxy/Tuple.cs b/Net3-Proxy/Tuple.cs index 5caebc5e..af726fda 100644 --- a/Net3-Proxy/Tuple.cs +++ b/Net3-Proxy/Tuple.cs @@ -1,53 +1,93 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Net3_Proxy -{ - public static class Tuple - { - public static Tuple Create(T1 i1, T2 i2) - => new Tuple(i1, i2); - - internal static int CombineHashCodes(int h1, int h2) - { - return (h1 << 5) + h1 ^ h2; - } - } - - [Serializable] - public class Tuple : IComparable - { - public T1 Item1 { get; private set; } - public T2 Item2 { get; private set; } - - public Tuple(T1 item1, T2 item2) - { - Item1 = item1; - Item2 = item2; - } - - public override bool Equals(object obj) - => obj is Tuple tup - && Equals(Item1, tup.Item1) - && Equals(Item2, tup.Item2); - - int IComparable.CompareTo(object obj) - { - if (obj == null) return 1; - var tup = obj as Tuple; - if (tup == null) throw new ArgumentException($"Argument must be of type {GetType()}.", "other"); - int num = Comparer.Default.Compare(Item1, tup.Item1); - if (num != 0) return num; - return Comparer.Default.Compare(Item2, tup.Item2); - } - - public override int GetHashCode() => - Tuple.CombineHashCodes(EqualityComparer.Default.GetHashCode(Item1), EqualityComparer.Default.GetHashCode(Item2)); - - public override string ToString() => - $"({Item1}, {Item2})"; - } -} +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Net3_Proxy +{ + public static class Tuple + { + public static Tuple Create(T1 i1, T2 i2) + => new Tuple(i1, i2); + + internal static int CombineHashCodes(int h1, int h2) + { + return (h1 << 5) + h1 ^ h2; + } + } + + [Serializable] + public class Tuple : IComparable, IComparable> + { + public T1 Item1 { get; private set; } + public T2 Item2 { get; private set; } + + public Tuple(T1 item1, T2 item2) + { + Item1 = item1; + Item2 = item2; + } + + public override bool Equals(object obj) + => obj is Tuple tup + && Equals(Item1, tup.Item1) + && Equals(Item2, tup.Item2); + + int IComparable.CompareTo(object obj) + { + if (obj == null) return 1; + var tup = obj as Tuple; + if (tup == null) throw new ArgumentException($"Argument must be of type {GetType()}.", "other"); + return CompareTo(tup); + } + + public override int GetHashCode() => + Tuple.CombineHashCodes(EqualityComparer.Default.GetHashCode(Item1), EqualityComparer.Default.GetHashCode(Item2)); + + public override string ToString() => + $"({Item1}, {Item2})"; + + public int CompareTo(Tuple tup) + { + int num = Comparer.Default.Compare(Item1, tup.Item1); + if (num != 0) return num; + return Comparer.Default.Compare(Item2, tup.Item2); + } + + public static bool operator ==(Tuple left, Tuple right) + { + if (ReferenceEquals(left, null)) + { + return ReferenceEquals(right, null); + } + + return left.Equals(right); + } + + public static bool operator !=(Tuple left, Tuple right) + { + return !(left == right); + } + + public static bool operator <(Tuple left, Tuple right) + { + return ReferenceEquals(left, null) ? !ReferenceEquals(right, null) : left.CompareTo(right) < 0; + } + + public static bool operator <=(Tuple left, Tuple right) + { + return ReferenceEquals(left, null) || left.CompareTo(right) <= 0; + } + + public static bool operator >(Tuple left, Tuple right) + { + return !ReferenceEquals(left, null) && left.CompareTo(right) > 0; + } + + public static bool operator >=(Tuple left, Tuple right) + { + return ReferenceEquals(left, null) ? ReferenceEquals(right, null) : left.CompareTo(right) >= 0; + } + } +} diff --git a/Net3-Proxy/TypeUtils.cs b/Net3-Proxy/TypeUtils.cs new file mode 100644 index 00000000..c0981ff1 --- /dev/null +++ b/Net3-Proxy/TypeUtils.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Net3_Proxy +{ + internal static class TypeUtils + { + public static void ValidateType(Type type, string paramName) + => ValidateType(type, paramName, false, false); + + // Token: 0x0600197D RID: 6525 RVA: 0x00053C07 File Offset: 0x00051E07 + public static void ValidateType(Type type, string paramName, bool allowByRef, bool allowPointer) + { + if (ValidateType(type, paramName, -1)) + { + if (!allowByRef && type.IsByRef) + { + throw new ArgumentException("Type must not be ref", paramName); + } + if (!allowPointer && type.IsPointer) + { + throw new ArgumentException("Type must not be pointer", paramName); + } + } + } + + // Token: 0x0600197E RID: 6526 RVA: 0x00053C37 File Offset: 0x00051E37 + public static bool ValidateType(Type type, string paramName, int index) + { + if (type == typeof(void)) + return false; + if (type.ContainsGenericParameters) + throw type.IsGenericTypeDefinition + ? new ArgumentException($"Type {type} is a generic type definition", GetParamName(paramName, index)) + : new ArgumentException($"Type {type} contains generic parameters", GetParamName(paramName, index)); + return true; + } + + public static string GetParamName(string paramName, int index) + { + if (index >= 0) + { + return string.Format("{0}[{1}]", paramName, index); + } + return paramName; + } + + public static bool AreEquivalent(Type t1, Type t2) + { + return t1 != null && t1 == t2; + } + + public static bool AreReferenceAssignable(Type dest, Type src) + { + return TypeUtils.AreEquivalent(dest, src) || (!dest.IsValueType && !src.IsValueType && dest.IsAssignableFrom(src)); + } + } +} diff --git a/Net3-Proxy/Utils.cs b/Net3-Proxy/Utils.cs index 9c0c66c4..c8869a2c 100644 --- a/Net3-Proxy/Utils.cs +++ b/Net3-Proxy/Utils.cs @@ -1,26 +1,49 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Net3_Proxy -{ - internal static class Utils - { - public static bool IsNullOrWhiteSpace(string value) - { - if (value == null) - { - return true; - } - for (int i = 0; i < value.Length; i++) - { - if (!char.IsWhiteSpace(value[i])) - { - return false; - } - } - return true; - } - } -} +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Net3_Proxy +{ + internal static class Utils + { + public static bool IsNullOrWhiteSpace(string value) + { + if (value == null) + { + return true; + } + for (int i = 0; i < value.Length; i++) + { + if (!char.IsWhiteSpace(value[i])) + { + return false; + } + } + return true; + } + public static IEnumerable Prepend(this IEnumerable seq, T prep) + => new PrependEnumerable(seq, prep); + + private sealed class PrependEnumerable : IEnumerable + { + private readonly IEnumerable rest; + private readonly T first; + + public PrependEnumerable(IEnumerable rest, T first) + { + this.rest = rest; + this.first = first; + } + + public IEnumerator GetEnumerator() + { // TODO: a custom impl that is less garbage + yield return first; + foreach (var v in rest) yield return v; + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + } +}