Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion ExposedObject/Exposed.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,12 @@ private Exposed(Type type)
/// <summary>
/// Gets the <see cref="Type"/> of the exposed object.
/// </summary>
internal Type SubjectType { get; private set; }
public Type SubjectType { get; private set; }

/// <summary>
/// Gets the wrapped value.
/// </summary>
public object? Value => value;

/// <summary>
/// Creates a new wrapper for accessing members of subject.
Expand Down
71 changes: 57 additions & 14 deletions ExposedObject/MetaObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ internal sealed class MetaObject : DynamicMetaObject
/// </summary>
private readonly bool isStatic;

private Exposed ExposedInstance => (Exposed)Value!;
private Type SubjectType => ExposedInstance.SubjectType;

/// <summary>
/// Initializes a new instance of the <see cref="MetaObject"/> class.
/// </summary>
Expand All @@ -54,7 +57,7 @@ internal sealed class MetaObject : DynamicMetaObject
/// <param name="staticBind">
/// Should this MetaObject bind to <see langword="static"/> or instance methods and fields.
/// </param>
public MetaObject(Expression expression, object value, bool staticBind) :
public MetaObject(Expression expression, Exposed value, bool staticBind) :
base(expression, BindingRestrictions.Empty, value)
{
isStatic = staticBind;
Expand All @@ -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];
Expand All @@ -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;
Expand All @@ -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));
}



/// <summary>
/// Performs the binding of the dynamic get member operation.
/// </summary>
Expand All @@ -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);
}


/// <summary>
/// Performs the binding of the dynamic set member operation.
/// </summary>
Expand All @@ -157,21 +199,25 @@ 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);
}

/// <summary>
/// Generates the <see cref="Expression"/> for accessing a member by name.
/// </summary>
/// <param name="self">
/// <param name="this">
/// The <see cref="Expression"/> for accessing the <see cref="Exposed"/> instance.
/// </param>
/// <param name="memberName">
Expand All @@ -182,17 +228,14 @@ public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicM
/// </returns>
/// <exception cref="MissingMemberException">
/// </exception>
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
Expand Down
37 changes: 37 additions & 0 deletions TestSubjects/SimilarClasses.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
39 changes: 39 additions & 0 deletions Tests/SimilarClassesTest.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}