Тестирование типа утки с помощью С# 4 для динамических объектов
Я хочу иметь простой пример утиного набора текста в С#, используя динамические объекты. Мне кажется, что динамический объект должен иметь методы HasValue/HasProperty/HasMethod с одним строковым параметром для имени значения, свойства или метода, которые вы ищете, прежде чем пытаться выполнить против него. Я стараюсь избегать блоков try/catch и более глубокого отражения, если это возможно. Кажется, это обычная практика для утиной печати на динамических языках (JS, Ruby, Python и т.д.), Которая должна проверять свойство/метод, прежде чем пытаться его использовать, затем вернуться к умолчанию или выбросить контролируемое исключение, Ниже приведен пример, который я хочу выполнить.
Если описанные выше методы не существуют, у кого-нибудь есть готовые методы расширения для динамических, которые это сделают?
Пример. В JavaScript я могу легко протестировать метод на объекте.
//JavaScript
function quack(duck) {
if (duck && typeof duck.quack === "function") {
return duck.quack();
}
return null; //nothing to return, not a duck
}
Как бы я сделал то же самое в С#?
//C# 4
dynamic Quack(dynamic duck)
{
//how do I test that the duck is not null,
//and has a quack method?
//if it doesn't quack, return null
}
Ответы
Ответ 1
Попробуйте следующее:
using System.Linq;
using System.Reflection;
//...
public dynamic Quack(dynamic duck, int i)
{
Object obj = duck as Object;
if (duck != null)
{
//check if object has method Quack()
MethodInfo method = obj.GetType().GetMethods().
FirstOrDefault(x => x.Name == "Quack");
//if yes
if (method != null)
{
//invoke and return value
return method.Invoke((object)duck, null);
}
}
return null;
}
Или это (использует только динамический):
public static dynamic Quack(dynamic duck)
{
try
{
//invoke and return value
return duck.Quack();
}
//thrown if method call failed
catch (RuntimeBinderException)
{
return null;
}
}
Ответ 2
Если у вас есть контроль над всеми типами объектов, которые вы будете использовать динамически, другой вариант заключается в том, чтобы заставить их наследовать от подкласса класса DynamicObject
, который скроен, чтобы не сбой, когда метод, который не выполняет существует:
Быстрая и грязная версия будет выглядеть так:
public class DynamicAnimal : DynamicObject
{
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
{
bool success = base.TryInvokeMember(binder, args, out result);
// If the method didn't exist, ensure the result is null
if (!success) result = null;
// Always return true to avoid Exceptions being raised
return true;
}
}
Затем вы можете сделать следующее:
public class Duck : DynamicAnimal
{
public string Quack()
{
return "QUACK!";
}
}
public class Cow : DynamicAnimal
{
public string Moo()
{
return "Mooooo!";
}
}
class Program
{
static void Main(string[] args)
{
var duck = new Duck();
var cow = new Cow();
Console.WriteLine("Can a duck quack?");
Console.WriteLine(DoQuack(duck));
Console.WriteLine("Can a cow quack?");
Console.WriteLine(DoQuack(cow));
Console.ReadKey();
}
public static string DoQuack(dynamic animal)
{
string result = animal.Quack();
return result ?? "... silence ...";
}
}
И ваш результат будет:
Can a duck quack?
QUACK!
Can a cow quack?
... silence ...
Изменить: я должен отметить, что это верхушка айсберга, если вы можете использовать этот подход и основываться на DynamicObject
. Вы могли бы написать такие методы, как bool HasMember(string memberName)
, если хотите.
Ответ 3
Реализация метода HasProperty для каждого IDynamicMetaObjectProvider БЕЗ бросания RuntimeBinderException.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Dynamic;
using Microsoft.CSharp.RuntimeBinder;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
namespace DynamicCheckPropertyExistence
{
class Program
{
static void Main(string[] args)
{
dynamic testDynamicObject = new ExpandoObject();
testDynamicObject.Name = "Testovaci vlastnost";
Console.WriteLine(HasProperty(testDynamicObject, "Name"));
Console.WriteLine(HasProperty(testDynamicObject, "Id"));
Console.ReadLine();
}
private static bool HasProperty(IDynamicMetaObjectProvider dynamicProvider, string name)
{
var defaultBinder = Binder.GetMember(CSharpBinderFlags.None, name, typeof(Program),
new[]
{
CSharpArgumentInfo.Create(
CSharpArgumentInfoFlags.None, null)
}) as GetMemberBinder;
var callSite = CallSite<Func<CallSite, object, object>>.Create(new NoThrowGetBinderMember(name, false, defaultBinder));
var result = callSite.Target(callSite, dynamicProvider);
if (Object.ReferenceEquals(result, NoThrowExpressionVisitor.DUMMY_RESULT))
{
return false;
}
return true;
}
}
class NoThrowGetBinderMember : GetMemberBinder
{
private GetMemberBinder m_innerBinder;
public NoThrowGetBinderMember(string name, bool ignoreCase, GetMemberBinder innerBinder) : base(name, ignoreCase)
{
m_innerBinder = innerBinder;
}
public override DynamicMetaObject FallbackGetMember(DynamicMetaObject target, DynamicMetaObject errorSuggestion)
{
var retMetaObject = m_innerBinder.Bind(target, new DynamicMetaObject[] {});
var noThrowVisitor = new NoThrowExpressionVisitor();
var resultExpression = noThrowVisitor.Visit(retMetaObject.Expression);
var finalMetaObject = new DynamicMetaObject(resultExpression, retMetaObject.Restrictions);
return finalMetaObject;
}
}
class NoThrowExpressionVisitor : ExpressionVisitor
{
public static readonly object DUMMY_RESULT = new DummyBindingResult();
public NoThrowExpressionVisitor()
{
}
protected override Expression VisitConditional(ConditionalExpression node)
{
if (node.IfFalse.NodeType != ExpressionType.Throw)
{
return base.VisitConditional(node);
}
Expression<Func<Object>> dummyFalseResult = () => DUMMY_RESULT;
var invokeDummyFalseResult = Expression.Invoke(dummyFalseResult, null);
return Expression.Condition(node.Test, node.IfTrue, invokeDummyFalseResult);
}
private class DummyBindingResult {}
}
}
Ответ 4
http://code.google.com/p/impromptu-interface/ Кажется хорошим интерфейсом для динамических объектов... Это немного больше работы, чем я надеялся, но кажется, является самой чистой реализацией представленных примеров... Сохранение ответа Саймона как правильное, так как оно все еще самое близкое к тому, что я хотел, но методы интерфейса Impromptu действительно приятны.
Ответ 5
Самый короткий путь - это вызвать его и обработать исключение, если метод не существует. Я пришел из Python, где такой метод распространен в утиной печати, но я не знаю, широко ли он используется в С# 4...
Я не тестировал себя, так как у меня нет VC 2010 на моей машине
dynamic Quack(dynamic duck)
{
try
{
return duck.Quack();
}
catch (RuntimeBinderException)
{ return null; }
}