Skip to content

Commit

Permalink
Fix operator support
Browse files Browse the repository at this point in the history
  • Loading branch information
JLChnToZ committed May 30, 2023
1 parent eaf8772 commit 40b2a2d
Show file tree
Hide file tree
Showing 5 changed files with 303 additions and 54 deletions.
49 changes: 49 additions & 0 deletions JLChnToZ.CommonUtils.Dynamic.Test/LimitlessTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,55 @@ public void TestWrap() {
TestInterfaceAccess(limitless);
limitless = Limitless.Wrap(Limitless.Wrap(testSubject), "JLChnToZ.CommonUtils.Dynamic.Test.IInterface, JLChnToZ.CommonUtils.Dynamic.Test");
TestInterfaceAccess(limitless);
limitless = Limitless.Wrap(1, typeof(TestSubject));
TestLimitless(limitless, true);
}

[TestMethod]
public void TestOperators() {
var testSubject = new TestSubject();
var testSubject2 = new TestSubject();
var limitless = Limitless.Wrap(testSubject);
var limitless2 = Limitless.Wrap(testSubject2);
object _;
_ = limitless + limitless2; Assert.AreSame(TestSubject.lastCalledMethod, "operator+", $"Call operator on 2 wrapped objects + (Actual = {TestSubject.lastCalledMethod})");
_ = limitless - limitless2; Assert.AreSame(TestSubject.lastCalledMethod, "operator-", $"Call operator on 2 wrapped objects - (Actual = {TestSubject.lastCalledMethod})");
_ = limitless * limitless2; Assert.AreSame(TestSubject.lastCalledMethod, "operator*", $"Call operator on 2 wrapped objects * (Actual = {TestSubject.lastCalledMethod})");
_ = limitless / limitless2; Assert.AreSame(TestSubject.lastCalledMethod, "operator/", $"Call operator on 2 wrapped objects / (Actual = {TestSubject.lastCalledMethod})");
_ = limitless % limitless2; Assert.AreSame(TestSubject.lastCalledMethod, "operator%", $"Call operator on 2 wrapped objects % (Actual = {TestSubject.lastCalledMethod})");
_ = limitless & limitless2; Assert.AreSame(TestSubject.lastCalledMethod, "operator&", $"Call operator on 2 wrapped objects & (Actual = {TestSubject.lastCalledMethod})");
_ = limitless | limitless2; Assert.AreSame(TestSubject.lastCalledMethod, "operator|", $"Call operator on 2 wrapped objects | (Actual = {TestSubject.lastCalledMethod})");
_ = limitless ^ limitless2; Assert.AreSame(TestSubject.lastCalledMethod, "operator^", $"Call operator on 2 wrapped objects ^ (Actual = {TestSubject.lastCalledMethod})");
_ = limitless << 1; Assert.AreSame(TestSubject.lastCalledMethod, "operator<<", $"Call operator on 2 wrapped objects << (Actual = {TestSubject.lastCalledMethod})");
_ = limitless >> 1; Assert.AreSame(TestSubject.lastCalledMethod, "operator>>", $"Call operator on 2 wrapped objects >> (Actual = {TestSubject.lastCalledMethod})");
_ = limitless++; Assert.AreSame(TestSubject.lastCalledMethod, "operator++", $"Call operator on 1 wrapped object ++ (Actual = {TestSubject.lastCalledMethod})");
_ = limitless--; Assert.AreSame(TestSubject.lastCalledMethod, "operator--", $"Call operator on 1 wrapped object -- (Actual = {TestSubject.lastCalledMethod})");
_ = limitless += limitless2; Assert.AreSame(TestSubject.lastCalledMethod, "operator+", $"Call operator on 2 wrapped objects += (Actual = {TestSubject.lastCalledMethod})");
_ = limitless -= limitless2; Assert.AreSame(TestSubject.lastCalledMethod, "operator-", $"Call operator on 2 wrapped objects -= (Actual = {TestSubject.lastCalledMethod})");
_ = limitless *= limitless2; Assert.AreSame(TestSubject.lastCalledMethod, "operator*", $"Call operator on 2 wrapped objects *= (Actual = {TestSubject.lastCalledMethod})");
_ = limitless /= limitless2; Assert.AreSame(TestSubject.lastCalledMethod, "operator/", $"Call operator on 2 wrapped objects /= (Actual = {TestSubject.lastCalledMethod})");
_ = limitless %= limitless2; Assert.AreSame(TestSubject.lastCalledMethod, "operator%", $"Call operator on 2 wrapped objects %= (Actual = {TestSubject.lastCalledMethod})");
_ = limitless &= limitless2; Assert.AreSame(TestSubject.lastCalledMethod, "operator&", $"Call operator on 2 wrapped objects &= (Actual = {TestSubject.lastCalledMethod})");
_ = limitless |= limitless2; Assert.AreSame(TestSubject.lastCalledMethod, "operator|", $"Call operator on 2 wrapped objects |= (Actual = {TestSubject.lastCalledMethod})");
_ = limitless ^= limitless2; Assert.AreSame(TestSubject.lastCalledMethod, "operator^", $"Call operator on 2 wrapped objects ^= (Actual = {TestSubject.lastCalledMethod})");
_ = limitless <<= 1; Assert.AreSame(TestSubject.lastCalledMethod, "operator<<", $"Call operator on 2 wrapped objects <<= (Actual = {TestSubject.lastCalledMethod})");
_ = limitless >>= 1; Assert.AreSame(TestSubject.lastCalledMethod, "operator>>", $"Call operator on 2 wrapped objects >>= (Actual = {TestSubject.lastCalledMethod})");
_ = limitless == limitless2; Assert.AreSame(TestSubject.lastCalledMethod, "operator==", $"Call operator on 2 wrapped objects == (Actual = {TestSubject.lastCalledMethod})");
_ = limitless != limitless2; Assert.AreSame(TestSubject.lastCalledMethod, "operator!=", $"Call operator on 2 wrapped objects != (Actual = {TestSubject.lastCalledMethod})");
_ = limitless > limitless2; Assert.AreSame(TestSubject.lastCalledMethod, "operator>", $"Call operator on 2 wrapped objects > (Actual = {TestSubject.lastCalledMethod})");
_ = limitless < limitless2; Assert.AreSame(TestSubject.lastCalledMethod, "operator<", $"Call operator on 2 wrapped objects < (Actual = {TestSubject.lastCalledMethod})");
_ = limitless >= limitless2; Assert.AreSame(TestSubject.lastCalledMethod, "operator>=", $"Call operator on 2 wrapped objects >= (Actual = {TestSubject.lastCalledMethod})");
_ = limitless <= limitless2; Assert.AreSame(TestSubject.lastCalledMethod, "operator<=", $"Call operator on 2 wrapped objects <= (Actual = {TestSubject.lastCalledMethod})");
_ = limitless + testSubject2; Assert.AreSame(TestSubject.lastCalledMethod, "operator+", $"Call operator to unwrapped object + (Actual = {TestSubject.lastCalledMethod})");
_ = limitless - testSubject2; Assert.AreSame(TestSubject.lastCalledMethod, "operator-", $"Call operator to unwrapped object - (Actual = {TestSubject.lastCalledMethod})");
_ = limitless * testSubject2; Assert.AreSame(TestSubject.lastCalledMethod, "operator*", $"Call operator to unwrapped object * (Actual = {TestSubject.lastCalledMethod})");
_ = limitless / testSubject2; Assert.AreSame(TestSubject.lastCalledMethod, "operator/", $"Call operator to unwrapped object / (Actual = {TestSubject.lastCalledMethod})");
_ = limitless % testSubject2; Assert.AreSame(TestSubject.lastCalledMethod, "operator%", $"Call operator to unwrapped object % (Actual = {TestSubject.lastCalledMethod})");
_ = limitless & testSubject2; Assert.AreSame(TestSubject.lastCalledMethod, "operator&", $"Call operator to unwrapped object & (Actual = {TestSubject.lastCalledMethod})");
_ = limitless | testSubject2; Assert.AreSame(TestSubject.lastCalledMethod, "operator|", $"Call operator to unwrapped object | (Actual = {TestSubject.lastCalledMethod})");
_ = limitless ^ testSubject2; Assert.AreSame(TestSubject.lastCalledMethod, "operator^", $"Call operator to unwrapped object ^ (Actual = {TestSubject.lastCalledMethod})");
_ = limitless == testSubject2; Assert.AreSame(TestSubject.lastCalledMethod, "operator==", $"Call operator to unwrapped object == (Actual = {TestSubject.lastCalledMethod})");
_ = limitless != testSubject2; Assert.AreSame(TestSubject.lastCalledMethod, "operator!=", $"Call operator to unwrapped object != (Actual = {TestSubject.lastCalledMethod})");
}

[TestMethod]
Expand Down
185 changes: 173 additions & 12 deletions JLChnToZ.CommonUtils.Dynamic.Test/TestSubject.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace JLChnToZ.CommonUtils.Dynamic.Test {
namespace JLChnToZ.CommonUtils.Dynamic.Test {
internal class TestSubject: IInterface {
public static string lastCalledMethod;

private static string privateStaticStringField = "privateStaticStringField";

Expand All @@ -15,67 +10,233 @@ internal class TestSubject: IInterface {

private string this[string key] {
get {
lastCalledMethod = "this.get";
undelyDictionary.TryGetValue(key, out string value);
return value;
}
set => undelyDictionary[key] = value;
set {
lastCalledMethod = "this.set";
undelyDictionary[key] = value;
}
}

private static string PrivateStaticStringProperty {
get {
lastCalledMethod = "PrivateStaticStringProperty.get";
return privateStaticStringField;
}
set {
lastCalledMethod = "PrivateStaticStringProperty.set";
privateStaticStringField = value;
}
}

private static string PrivateStaticStringProperty { get; set; }
private string PrivateStringProperty {
get {
lastCalledMethod = "PrivateStringProperty.get";
return privateStringField;
}
set {
lastCalledMethod = "PrivateStringProperty.set";
privateStringField = value;
}
}

private string PrivateStringProperty { get; set; }

bool IInterface.HiddenInterfaceProperty { get; set; }
bool hiddenInterfacePropertyValue;
bool IInterface.HiddenInterfaceProperty {
get {
lastCalledMethod = "HiddenInterfaceProperty.get";
return hiddenInterfacePropertyValue;
}
set {
lastCalledMethod = "HiddenInterfaceProperty.set";
hiddenInterfacePropertyValue = value;
}
}

public TestSubject() {}
public TestSubject() {
lastCalledMethod = "ctor0";
}

private TestSubject(string testString) {
lastCalledMethod = "ctor1";
privateStringField = testString;
}


private static bool PrivateStaticMethod(bool par) {
lastCalledMethod = "PrivateStaticMethodBool";
return par;
}

private static T HiddenStaticGenericMethod<T>(T value) {
lastCalledMethod = "HiddenStaticGenericMethodT";
return value;
}

private object PrivateMethod(object par) {
lastCalledMethod = "PrivateMethodObject";
return null;
}

private bool PrivateMethod(bool par) {
lastCalledMethod = "PrivateMethodBool";
return par;
}

private int PrivateMethod(int par) {
lastCalledMethod = "PrivateMethodInt";
return par;
}

private T HiddenGenericMethod<T>(T value) {
lastCalledMethod = "HiddenGenericMethodT";
return value;
}

bool IInterface.HiddenInterfaceMethod(bool par) {
lastCalledMethod = "HiddenInterfaceMethodBool";
return par;
}

public static IEnumerable<object> EnumerableMethod() {
lastCalledMethod = "EnumerableMethod";
for (int i = 0; i < 5; i++)
yield return new HiddenSubClass();
}

static async Task<int> AwaitableMethod(int input) {
lastCalledMethod = "AwaitableMethod";
await Task.Delay(100);
return input;
}

static async Task AwaitThrowTask() {
lastCalledMethod = "AwaitThrowTask";
await Task.Delay(100);
throw new Exception();
}
public static TestSubject operator +(TestSubject a, TestSubject b) {
lastCalledMethod = "operator+";
return a;
}

public static TestSubject operator -(TestSubject a, TestSubject b) {
lastCalledMethod = "operator-";
return a;
}

public static TestSubject operator *(TestSubject a, TestSubject b) {
lastCalledMethod = "operator*";
return a;
}

public static TestSubject operator /(TestSubject a, TestSubject b) {
lastCalledMethod = "operator/";
return a;
}

public static TestSubject operator %(TestSubject a, TestSubject b) {
lastCalledMethod = "operator%";
return a;
}

public static TestSubject operator &(TestSubject a, TestSubject b) {
lastCalledMethod = "operator&";
return a;
}

public static TestSubject operator |(TestSubject a, TestSubject b) {
lastCalledMethod = "operator|";
return a;
}

public static TestSubject operator ^(TestSubject a, TestSubject b) {
lastCalledMethod = "operator^";
return a;
}

public static TestSubject operator <<(TestSubject a, int b) {
lastCalledMethod = "operator<<";
return a;
}

public static TestSubject operator >>(TestSubject a, int b) {
lastCalledMethod = "operator>>";
return a;
}

public static TestSubject operator ~(TestSubject a) {
lastCalledMethod = "operator~";
return a;
}

public static TestSubject operator !(TestSubject a) {
lastCalledMethod = "operator!";
return a;
}

public static TestSubject operator ++(TestSubject a) {
lastCalledMethod = "operator++";
return a;
}

public static TestSubject operator --(TestSubject a) {
lastCalledMethod = "operator--";
return a;
}

public static TestSubject operator +(TestSubject a) {
lastCalledMethod = "operator+";
return a;
}

public static TestSubject operator -(TestSubject a) {
lastCalledMethod = "operator-";
return a;
}

public static bool operator ==(TestSubject a, TestSubject b) {
lastCalledMethod = "operator==";
return true;
}

public static bool operator !=(TestSubject a, TestSubject b) {
lastCalledMethod = "operator!=";
return true;
}

public static bool operator <(TestSubject a, TestSubject b) {
lastCalledMethod = "operator<";
return true;
}

public static bool operator >(TestSubject a, TestSubject b) {
lastCalledMethod = "operator>";
return true;
}

public static bool operator <=(TestSubject a, TestSubject b) {
lastCalledMethod = "operator<=";
return true;
}

public static bool operator >=(TestSubject a, TestSubject b) {
lastCalledMethod = "operator>=";
return true;
}

public static implicit operator TestSubject(int a) {
var obj = new TestSubject();
lastCalledMethod = "operator int";
return obj;
}

public static explicit operator string(TestSubject a) {
lastCalledMethod = "operator string";
return "test";
}

private struct HiddenSubClass {
private static string privateStaticStringField = "privateStaticStringField";
Expand Down
46 changes: 37 additions & 9 deletions JLChnToZ.CommonUtils.Dynamic/Limitless.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections;
using System.Dynamic;
using System.Linq.Expressions;

namespace JLChnToZ.CommonUtils.Dynamic {
using static Utilites;
Expand Down Expand Up @@ -51,15 +52,22 @@ public static dynamic Static(Type type) {

/// <summary>Wrap an object to dynamic object.</summary>
/// <param name="obj">The object to wrap.</param>
/// <param name="type">Optional base/interface type to wrap.</param>
/// <param name="toType">Optional base/interface type to wrap.</param>
/// <returns>The dynamic object with access to all members of it.</returns>
public static dynamic Wrap(object obj, Type type = null) {
if (obj == null || (obj is Limitless && type == null) || obj is LimitlessInvokable) return obj;
if (type == null) return InternalWrap(obj);
obj = InternalUnwrap(obj);
if (!type.IsAssignableFrom(obj.GetType()))
throw new ArgumentException("Type mismatch", nameof(type));
return new Limitless(obj, type);
public static dynamic Wrap(object obj, Type toType = null) {
if (obj == null || (obj is Limitless && toType == null) || obj is LimitlessInvokable) return obj;
if (toType == null) return InternalWrap(obj);
object result;
var fromType = obj.GetType();
if (obj is Limitless wrapped) {
if (wrapped.typeInfo.TryCast(wrapped.target, toType, false, out result))
return InternalWrap(result, toType);
obj = wrapped.target;
} else if (TypeInfo.Get(fromType).TryCast(obj, toType, false, out result))
return InternalWrap(result, toType);
if (TypeInfo.Get(toType).TryCast(obj, fromType, true, out result))
return InternalWrap(result, toType);
throw new ArgumentException("Type mismatch", nameof(toType));
}

/// <summary>Wrap an object to dynamic object.</summary>
Expand Down Expand Up @@ -179,7 +187,7 @@ public override bool TryUnaryOperation(UnaryOperationBinder binder, out object r
(target is DynamicObject dynamicObject && dynamicObject.TryUnaryOperation(binder, out result));

public override bool TryConvert(ConvertBinder binder, out object result) {
if (typeInfo.TryCast(target, binder.Type, out result))
if (typeInfo.TryCast(target, binder.Type, false, out result))
return true;
try {
if (target is IConvertible convertible) {
Expand Down Expand Up @@ -210,6 +218,26 @@ protected virtual IEnumerator GetEnumerator() {
public override string ToString() => target?.ToString() ?? type.ToString();

public override int GetHashCode() => target?.GetHashCode() ?? 0;

public static bool operator ==(Limitless a, object b) {
if (b is Limitless wrapped) b = wrapped.target;
if (ReferenceEquals(a.target, b)) return true;
object result;
if (a.typeInfo.TryInvoke(null, "op_Equality", new[] { a.target, b }, out result)) return (bool)result;
if (b == null) return a.target == null;
if (TypeInfo.Get(b.GetType()).TryInvoke(null, "op_Equality", new[] { b, a.target }, out result)) return (bool)result;
return false;
}

public static bool operator !=(Limitless a, object b) {
if (b is Limitless wrapped) b = wrapped.target;
if (ReferenceEquals(a.target, b)) return false;
object result;
if (a.typeInfo.TryInvoke(null, "op_Inequality", new[] { a.target, b }, out result)) return (bool)result;
if (b == null) return a.target != null;
if (TypeInfo.Get(b.GetType()).TryInvoke(null, "op_Inequality", new[] { b, a.target }, out result)) return (bool)result;
return true;
}
}

}
Loading

0 comments on commit 40b2a2d

Please sign in to comment.