3 changed files with 157 additions and 0 deletions
@ -0,0 +1,91 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
using Avalonia.Data; |
|||
using System.Reflection; |
|||
using System.Linq; |
|||
|
|||
namespace Avalonia.Markup.Data.Plugins |
|||
{ |
|||
class MethodAccessorPlugin : IPropertyAccessorPlugin |
|||
{ |
|||
public bool Match(object obj, string propertyName) |
|||
=> obj.GetType().GetRuntimeMethods().FirstOrDefault(x => x.Name == propertyName) != null; |
|||
|
|||
public IPropertyAccessor Start(WeakReference reference, string propertyName) |
|||
{ |
|||
Contract.Requires<ArgumentNullException>(reference != null); |
|||
Contract.Requires<ArgumentNullException>(propertyName != null); |
|||
|
|||
var instance = reference.Target; |
|||
var method = instance.GetType().GetRuntimeMethods().FirstOrDefault(x => x.Name == propertyName); |
|||
|
|||
if (method != null) |
|||
{ |
|||
return new Accessor(reference, method); |
|||
} |
|||
else |
|||
{ |
|||
var message = $"Could not find CLR method '{propertyName}' on '{instance}'"; |
|||
var exception = new MissingMemberException(message); |
|||
return new PropertyError(new BindingNotification(exception, BindingErrorType.Error)); |
|||
} |
|||
} |
|||
|
|||
private class Accessor : PropertyAccessorBase |
|||
{ |
|||
public Accessor(WeakReference reference, MethodInfo method) |
|||
{ |
|||
Contract.Requires<ArgumentNullException>(reference != null); |
|||
Contract.Requires<ArgumentNullException>(method != null); |
|||
|
|||
var paramTypes = method.GetParameters().Select(param => param.ParameterType).ToArray(); |
|||
var returnType = method.ReturnType; |
|||
|
|||
// TODO: Throw exception if more than 8 parameters or more than 7 + return type.
|
|||
// Do this here or in the caller? Here probably
|
|||
if (returnType == typeof(void)) |
|||
{ |
|||
if (paramTypes.Length == 0) |
|||
{ |
|||
PropertyType = typeof(Action); |
|||
} |
|||
else |
|||
{ |
|||
PropertyType = Type.GetType($"System.Action`{paramTypes.Length}").MakeGenericType(paramTypes); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
var genericTypeParameters = paramTypes.Concat(new[] { returnType }).ToArray(); |
|||
PropertyType = Type.GetType($"System.Func`{genericTypeParameters.Length}").MakeGenericType(genericTypeParameters); |
|||
} |
|||
|
|||
// TODO: Is this going to leak?
|
|||
// TODO: Static methods?
|
|||
Value = method.CreateDelegate(PropertyType, reference.Target); |
|||
} |
|||
|
|||
public override Type PropertyType { get; } |
|||
|
|||
public override object Value { get; } |
|||
|
|||
public override bool SetValue(object value, BindingPriority priority) => false; |
|||
|
|||
protected override void SubscribeCore(IObserver<object> observer) |
|||
{ |
|||
SendCurrentValue(); |
|||
} |
|||
|
|||
private void SendCurrentValue() |
|||
{ |
|||
try |
|||
{ |
|||
var value = Value; |
|||
Observer.OnNext(value); |
|||
} |
|||
catch { } |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,65 @@ |
|||
using Avalonia.Data; |
|||
using Avalonia.Markup.Data; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Reactive.Linq; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Markup.UnitTests.Data |
|||
{ |
|||
public class ExpressionObserverTests_Method |
|||
{ |
|||
private class TestObject |
|||
{ |
|||
public void MethodWithoutReturn() { } |
|||
|
|||
public int MethodWithReturn() => 0; |
|||
|
|||
public int MethodWithReturnAndParameters(int i) => i; |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Get_Method() |
|||
{ |
|||
var data = new TestObject(); |
|||
var observer = new ExpressionObserver(data, nameof(TestObject.MethodWithoutReturn)); |
|||
var result = await observer.Take(1); |
|||
|
|||
Assert.NotNull(result); |
|||
|
|||
GC.KeepAlive(data); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(nameof(TestObject.MethodWithoutReturn), typeof(Action))] |
|||
[InlineData(nameof(TestObject.MethodWithReturn), typeof(Func<int>))] |
|||
[InlineData(nameof(TestObject.MethodWithReturnAndParameters), typeof(Func<int, int>))] |
|||
public async Task Should_Get_Method_WithCorrectDelegateType(string methodName, Type expectedType) |
|||
{ |
|||
var data = new TestObject(); |
|||
var observer = new ExpressionObserver(data, methodName); |
|||
var result = await observer.Take(1); |
|||
|
|||
Assert.IsType(expectedType, result); |
|||
|
|||
GC.KeepAlive(data); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Can_Call_Method_Returned_From_Observer() |
|||
{ |
|||
var data = new TestObject(); |
|||
var observer = new ExpressionObserver(data, nameof(TestObject.MethodWithReturnAndParameters)); |
|||
var result = await observer.Take(1); |
|||
|
|||
var callback = (Func<int, int>)result; |
|||
|
|||
Assert.Equal(1, callback(1)); |
|||
|
|||
GC.KeepAlive(data); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue