diff --git a/ExposedObject/Exposed.cs b/ExposedObject/Exposed.cs index 6332b67..cc077ea 100644 --- a/ExposedObject/Exposed.cs +++ b/ExposedObject/Exposed.cs @@ -79,7 +79,12 @@ private Exposed(Type type) /// /// Gets the of the exposed object. /// - internal Type SubjectType { get; private set; } + public Type SubjectType { get; private set; } + + /// + /// Gets the wrapped value. + /// + public object? Value => value; /// /// Creates a new wrapper for accessing members of subject. diff --git a/ExposedObject/MetaObject.cs b/ExposedObject/MetaObject.cs index 209c2e8..c46f4cb 100644 --- a/ExposedObject/MetaObject.cs +++ b/ExposedObject/MetaObject.cs @@ -42,6 +42,9 @@ internal sealed class MetaObject : DynamicMetaObject /// private readonly bool isStatic; + private Exposed ExposedInstance => (Exposed)Value!; + private Type SubjectType => ExposedInstance.SubjectType; + /// /// Initializes a new instance of the class. /// @@ -54,7 +57,7 @@ internal sealed class MetaObject : DynamicMetaObject /// /// Should this MetaObject bind to or instance methods and fields. /// - public MetaObject(Expression expression, object value, bool staticBind) : + public MetaObject(Expression expression, Exposed value, bool staticBind) : base(expression, BindingRestrictions.Empty, value) { isStatic = staticBind; @@ -78,7 +81,7 @@ public MetaObject(Expression expression, object value, bool staticBind) : public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args) { var self = Expression; - var exposed = (Exposed)Value; + var exposed = ExposedInstance; var argTypes = new Type[args.Length]; var argExps = new Expression[args.Length]; @@ -92,7 +95,7 @@ public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, Dy argTypes[i] = argTypes[i].MakeByRefType(); } - var type = exposed.SubjectType; + var type = SubjectType; var declaringType = type; #if EXPOSED_NULLABLE MethodInfo? method; @@ -110,16 +113,50 @@ public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, Dy throw new MissingMemberException(type.FullName, binder.Name); } - var @this = isStatic - ? null - : Expression.Convert(Expression.Field(Expression.Convert(self, typeof(Exposed)), "value"), type); + var thisRaw = GetThisExpressionRaw(self); + var @this = ConvertThis(thisRaw, type); var target = Expression.Call(@this, method, argExps); var restrictions = BindingRestrictions.GetTypeRestriction(self, typeof(Exposed)); + restrictions = AppendRestrictions(thisRaw, restrictions, self); return new DynamicMetaObject(ConvertExpressionType(binder.ReturnType, target), restrictions); } + private BindingRestrictions AppendRestrictions(Expression? thisRaw, BindingRestrictions restrictions, Expression self) + { + if (thisRaw is { } thisExp) + { + restrictions = restrictions.Merge(BindingRestrictions.GetTypeRestriction(thisExp, SubjectType)); + } + else + { + // Need to restrict the instance :( + restrictions = restrictions.Merge(BindingRestrictions.GetInstanceRestriction(self, ExposedInstance)); + } + + return restrictions; + } + + private static Expression? ConvertThis(Expression? rawThis, Type type) + { + if (rawThis != null) + { + return Expression.Convert(rawThis, type); + } + + return null; + } + + private Expression? GetThisExpressionRaw(Expression self) + { + return isStatic + ? null + : Expression.Property(Expression.Convert(self, typeof(Exposed)), nameof(Exposed.Value)); + } + + + /// /// Performs the binding of the dynamic get member operation. /// @@ -133,14 +170,19 @@ public override DynamicMetaObject BindGetMember(GetMemberBinder binder) { var self = Expression; - var memberExpression = GetMemberExpression(self, binder.Name); + var type = SubjectType; + var thisRaw = GetThisExpressionRaw(self); + var @this = ConvertThis(thisRaw, type); + var memberExpression = GetMemberExpression(@this, binder.Name); var target = Expression.Convert(memberExpression, binder.ReturnType); var restrictions = BindingRestrictions.GetTypeRestriction(self, typeof(Exposed)); + restrictions = AppendRestrictions(thisRaw, restrictions, self); return new DynamicMetaObject(target, restrictions); } + /// /// Performs the binding of the dynamic set member operation. /// @@ -157,13 +199,17 @@ public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicM { var self = Expression; - var memberExpression = GetMemberExpression(self, binder.Name); + var type = SubjectType; + var thisRaw = GetThisExpressionRaw(self); + var @this = ConvertThis(thisRaw, type); + var memberExpression = GetMemberExpression(@this, binder.Name); var target = Expression.Convert( Expression.Assign(memberExpression, Expression.Convert(value.Expression, memberExpression.Type)), binder.ReturnType); var restrictions = BindingRestrictions.GetTypeRestriction(self, typeof(Exposed)); + restrictions = AppendRestrictions(thisRaw, restrictions, self); return new DynamicMetaObject(target, restrictions); } @@ -171,7 +217,7 @@ public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicM /// /// Generates the for accessing a member by name. /// - /// + /// /// The for accessing the instance. /// /// @@ -182,17 +228,14 @@ public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicM /// /// /// - private MemberExpression GetMemberExpression(Expression self, string memberName) + private MemberExpression GetMemberExpression(Expression? @this, string memberName) { #if EXPOSED_NULLABLE MemberExpression? memberExpression = null; #else MemberExpression memberExpression = null; #endif - var type = ((Exposed)Value).SubjectType; - var @this = isStatic - ? null - : Expression.Convert(Expression.Field(Expression.Convert(self, typeof(Exposed)), "value"), type); + var type = SubjectType; var declaringType = type; do diff --git a/TestSubjects/SimilarClasses.cs b/TestSubjects/SimilarClasses.cs new file mode 100644 index 0000000..eed3947 --- /dev/null +++ b/TestSubjects/SimilarClasses.cs @@ -0,0 +1,37 @@ +using System.Diagnostics.CodeAnalysis; +// ReSharper disable UnusedType.Global +// ReSharper disable UnusedMember.Global +// ReSharper disable UnusedMember.Local +// ReSharper disable UnusedParameter.Global +// ReSharper disable UnusedParameter.Local + +namespace TestSubjects; + +[SuppressMessage("Performance", "CA1822:Mark members as static")] +public class SimilarClass1 +{ + public static string SimilarMethod(int arg) + { + return nameof(SimilarClass1); + } + + private string InstanceMethod(int arg) + { + return nameof(SimilarClass1); + } +} + + +[SuppressMessage("Performance", "CA1822:Mark members as static")] +public class SimilarClass2 +{ + public static string SimilarMethod(int arg) + { + return nameof(SimilarClass2); + } + + private string InstanceMethod(int arg) + { + return nameof(SimilarClass2); + } +} \ No newline at end of file diff --git a/Tests/SimilarClassesTest.cs b/Tests/SimilarClassesTest.cs new file mode 100644 index 0000000..30a47ec --- /dev/null +++ b/Tests/SimilarClassesTest.cs @@ -0,0 +1,39 @@ +using System; +using TestSubjects; +using Xunit; + +namespace Tests; + + +public class SimilarClassTest +{ + [Fact] + public void TestSimilarClassesInstances() + { + dynamic exposed1 = Exposed.New(Type.GetType("TestSubjects.SimilarClass1, TestSubjects")); + string result1 = exposed1.InstanceMethod(1); + + dynamic exposed2 = Exposed.New(Type.GetType("TestSubjects.SimilarClass2, TestSubjects")); + string result2 = exposed2.InstanceMethod(2); + + // No failure + + Assert.Equal(nameof(SimilarClass1), result1); + Assert.Equal(nameof(SimilarClass2), result2); + } + + [Fact] + public void TestSimilarClassesStatic() + { + dynamic exposed1 = Exposed.From(Type.GetType("TestSubjects.SimilarClass1, ExposedObject.TestSubjects")); + string result1 = exposed1.SimilarMethod(1); + + dynamic exposed2 = Exposed.From(Type.GetType("TestSubjects.SimilarClass2, TestSubjects")); + string result2 = exposed2.SimilarMethod(2); + + // No failure + + Assert.Equal(nameof(SimilarClass1), result1); + Assert.Equal(nameof(SimilarClass2), result2); + } +} \ No newline at end of file