C# 4.0,检测方法是否缺失

7

我有一个情况,需要将LinePragmas添加到CodeDom对象中。但是有些代码dom对象具有LinePragma属性,而有些则没有。

所以我想知道是否可以使用动态关键字来检测对象上是否存在该属性(不会抛出异常),如果存在,则添加pragma。以下是我的当前方法:

public static T SetSource<T>(this T codeObject, INode sourceNode)
    where T : CodeObject
{
    codeObject.UserData["Node"] = sourceNode.Source;
    dynamic dynamicCodeObject = codeObject;

    // How can I not throw an exception here?
    if (dynamicCodeObject.LinePragma != null)
    {
        dynamicCodeObject.LinePragma = new CodeLinePragma(
        sourceNode.Source.Path.AbsoluteUri,
        sourceNode.Source.StartLine);
    }

    return codeObject;
}

更新: 我采用的解决方案是添加了一个名为Exists()的扩展方法。我在这里写了一篇博客文章: Member Exists Dynamic C# 4.0

要点是创建一个返回实现DynamicObject的TryGetMember的对象的扩展方法。它使用反射来返回true或false。这使您可以编写以下代码:

object instance = new { Foo = "Hello World!" };
if (instance.Reflection().Exists().Foo)
{
    string value = instance.Reflection().Call().Foo;
    Console.WriteLine(value);
}
6个回答

8

您可以在不使用C# 4.0的动态功能的情况下检测对象是否具有属性,而是使用已经存在一段时间的反射功能(至少在.NET 2.0中可用,不确定< 2.0)。

PropertyInfo info = codeObject.getType().GetProperty(
    "LinePragma", 
    BindingFlags.Public | BindingFlags.Instance
)

如果对象没有该属性,则GetProperty()将返回null。您可以对字段(GetField())和方法(GetMethod())执行类似操作。
不仅如此,一旦您拥有了PropertyInfo,就可以直接使用它来进行设置:
info.SetValue(codeObject, new CodeLinePragma(), null);

如果您不确定属性是否有设置方法,您可以采取更安全的方式:

MethodInfo method = info.GetSetMethod();
if(method != null)
    method.Invoke(codeObject, new object[]{ new CodeLinePragma() });

这也带来了额外的好处,比动态调用的查找开销更高效(找不到该语句的参考资料,所以我只是提出来)。我想这并没有直接回答你的问题,而是提供了一种实现相同目标的替代方案。我还没有使用过#4.0的功能(尽管我非常喜欢Ruby中可用的动态类型),它肯定不像动态解决方案那样干净/可读,但如果您不想抛出异常,那么可能是一个选择。编辑:正如@arbiter指出的那样,“这仅适用于本机.net动态对象。例如,对于IDispatch将无法工作。”

1
这仅适用于本机 .net 动态对象。例如,对于 IDispatch,这将无效。 - arbiter
@zvolkov,我不明白为什么你的答案表明这个方法行不通 - 你能详细解释一下吗? - Matt
这也是我最终所做的。我只是希望有一种更聪明、更优雅的方法来使用动态关键字完成这个任务。或者至少调用不存在的属性不会抛出异常 :( - justin.m.chase
如果目标类实现了IDynamicObject或其子类DynamicObject,则此方法将无法正常工作。有关详细信息,请参见下面的答案。 - Andriy Volkov
最终我采用了基于反射的方法。这是我的解决方案: http://justnbusiness.com/post/2009/07/02/Member-Exists-e28093-Dynamic-C-40.aspx基本上,它具有流畅的API风格,并且使用动态包装器来首先查看未知成员是否存在。 - justin.m.chase
显示剩余2条评论

5

我刚刚花了将近一个小时的时间搜索如何在动态语言中获得类似于Ruby的“RespondTo”方法。毫无疑问,这并不是一个简单的问题,但我还没有放弃。

反思后应该尝试的是这一点。

使用动态语言,到目前为止我所得到的唯一东西就是一个扩展方法,它将您的对象视为动态对象。如果它有效,则有效;如果无效,则静默失败...

public static void Dynamight<T>(this T target, Action<dynamic> action)
{
  dynamic d = target;
  try
  {
    action(d);
  }
  catch (RuntimeBinderException)
  {
    //That was that, didn't work out
  }
}

然后你可以做...
string h = "Hello";
h.Dynamight(d => Console.WriteLine(d.Length)); //Prints out 5
h.Dynamight(d => d.Foo()); //Nothing happens

更新:

由于我收到了负面评价,让我比扩展方法的微妙命名更加简洁:它是爆炸性(懂了吗?)。吞噬异常并不做任何事情是不好的。这不是生产代码,而是一个概念验证的版本1。我总是忘记在像stackoverflow这样的多千人论坛上不能太微妙。我错了。


很酷的东西。但是什么时候开始变得太聪明了? - Robert Harvey
1
我对那里的悄无声息的失败并不确定,听起来像是设置了一个让错误消息消失在黑暗中的场景。在Ruby中,如果没有方法,你仍然会得到运行时错误——除非这个类还定义了method_missing方法。即使这样,你也有选择按照自己的意愿处理它,并且如果你选择的话仍然会出现“未定义的方法”错误。 - Matt
2
扩展方法只会吞噬 RuntimeBinderException 异常,这在这种情况下是期望的行为。所有其他异常都将通过调用堆栈传递。我不认为这里有问题。 - Robert Harvey
我认为“might”拼写错误有点像一个笑话,因为这个方法可能不存在? - justin.m.chase

3

TryGetMember只存在于继承自DynamicObject的对象上...将其强制转换为(dynamic)并不完全等同于将其强制转换为(DynamicObject)。当您将其转换为dynamic并尝试调用其成员时,它将在动态包装器上调用TryGetValue,并在失败时抛出异常。我想可能可以像"(DynamicObject)(dynamic)obj"这样进行强制转换吗? - justin.m.chase

-1

我想插一句话,静态类型可以避免这个问题。

这是一个适合使用抽象方法和重写的候选方案。


问题在于,我正在尝试访问 CodeDom 对象上的一个属性。如果你不熟悉的话,可能有大约50个这样的对象,其中大约一半具有 LinePragma 属性。不幸的是,该属性没有在任何特定的共享基类型或接口中找到。因此,通过强类型,你必须尝试并失败地将大量对象进行强制转换,以找到正确的对象。非常繁琐。 - justin.m.chase

-1

想一想:由于目标类可以为不存在的成员提供其自己的成员查找和调用实现(通过实现IDynamicObject或子类化DynamicObject),验证成员是否存在的唯一方法是调用它并查看对象是否处理它或抛出异常

再次强调,处理不存在的成员是动态的!

--编辑--

如果您控制对象创建,则可以子类化该类并实现IDynamicObject以向其他类发出信号表明该方法不存在。

如果答案指出了真相-即在动态分派环境中除了调用成员之外,没有可靠的检查成员存在的方法,那么将其评为负面是不公平的。


这不是我的类型,我无法为其添加接口。 :( - justin.m.chase
此外,调用不存在的成员会抛出异常。我需要更多类似于TryInvoke的东西。 - justin.m.chase
1
这一切都很好,但问题是关于CodeDom对象的,它们不是动态的。他只是想知道C#4是否可以帮助发现该属性。另外,没有必要大写整个句子来试图表达观点。 - Jb Evain
标题是“C# 4.0,检测方法是否丢失”——这正是我解决的问题。关于大写字母的观点是正确的,已经修复。 - Andriy Volkov

-1
    using System.Collections.Generic;
using System.Linq.Expressions;

namespace System.Dynamic
{
    //
    // Summary:
    //     Provides a base class for specifying dynamic behavior at run time. This class
    //     must be inherited from; you cannot instantiate it directly.
    public class DynamicObject : IDynamicMetaObjectProvider
    {
        //
        // Summary:
        //     Enables derived types to initialize a new instance of the System.Dynamic.DynamicObject
        //     type.
        protected DynamicObject();

        //
        // Summary:
        //     Returns the enumeration of all dynamic member names.
        //
        // Returns:
        //     A sequence that contains dynamic member names.
        public virtual IEnumerable<string> GetDynamicMemberNames();
        //
        // Summary:
        //     Provides a System.Dynamic.DynamicMetaObject that dispatches to the dynamic virtual
        //     methods. The object can be encapsulated inside another System.Dynamic.DynamicMetaObject
        //     to provide custom behavior for individual actions. This method supports the Dynamic
        //     Language Runtime infrastructure for language implementers and it is not intended
        //     to be used directly from your code.
        //
        // Parameters:
        //   parameter:
        //     The expression that represents System.Dynamic.DynamicMetaObject to dispatch to
        //     the dynamic virtual methods.
        //
        // Returns:
        //     An object of the System.Dynamic.DynamicMetaObject type.
        public virtual DynamicMetaObject GetMetaObject(Expression parameter);
        //
        // Summary:
        //     Provides implementation for binary operations. Classes derived from the System.Dynamic.DynamicObject
        //     class can override this method to specify dynamic behavior for operations such
        //     as addition and multiplication.
        //
        // Parameters:
        //   binder:
        //     Provides information about the binary operation. The binder.Operation property
        //     returns an System.Linq.Expressions.ExpressionType object. For example, for the
        //     sum = first + second statement, where first and second are derived from the DynamicObject
        //     class, binder.Operation returns ExpressionType.Add.
        //
        //   arg:
        //     The right operand for the binary operation. For example, for the sum = first
        //     + second statement, where first and second are derived from the DynamicObject
        //     class, arg is equal to second.
        //
        //   result:
        //     The result of the binary operation.
        //
        // Returns:
        //     true if the operation is successful; otherwise, false. If this method returns
        //     false, the run-time binder of the language determines the behavior. (In most
        //     cases, a language-specific run-time exception is thrown.)
        public virtual bool TryBinaryOperation(BinaryOperationBinder binder, object arg, out object result);
        //
        // Summary:
        //     Provides implementation for type conversion operations. Classes derived from
        //     the System.Dynamic.DynamicObject class can override this method to specify dynamic
        //     behavior for operations that convert an object from one type to another.
        //
        // Parameters:
        //   binder:
        //     Provides information about the conversion operation. The binder.Type property
        //     provides the type to which the object must be converted. For example, for the
        //     statement (String)sampleObject in C# (CType(sampleObject, Type) in Visual Basic),
        //     where sampleObject is an instance of the class derived from the System.Dynamic.DynamicObject
        //     class, binder.Type returns the System.String type. The binder.Explicit property
        //     provides information about the kind of conversion that occurs. It returns true
        //     for explicit conversion and false for implicit conversion.
        //
        //   result:
        //     The result of the type conversion operation.
        //
        // Returns:
        //     true if the operation is successful; otherwise, false. If this method returns
        //     false, the run-time binder of the language determines the behavior. (In most
        //     cases, a language-specific run-time exception is thrown.)
        public virtual bool TryConvert(ConvertBinder binder, out object result);
        //
        // Summary:
        //     Provides the implementation for operations that initialize a new instance of
        //     a dynamic object. This method is not intended for use in C# or Visual Basic.
        //
        // Parameters:
        //   binder:
        //     Provides information about the initialization operation.
        //
        //   args:
        //     The arguments that are passed to the object during initialization. For example,
        //     for the new SampleType(100) operation, where SampleType is the type derived from
        //     the System.Dynamic.DynamicObject class, args[0] is equal to 100.
        //
        //   result:
        //     The result of the initialization.
        //
        // Returns:
        //     true if the operation is successful; otherwise, false. If this method returns
        //     false, the run-time binder of the language determines the behavior. (In most
        //     cases, a language-specific run-time exception is thrown.)
        public virtual bool TryCreateInstance(CreateInstanceBinder binder, object[] args, out object result);
        //
        // Summary:
        //     Provides the implementation for operations that delete an object by index. This
        //     method is not intended for use in C# or Visual Basic.
        //
        // Parameters:
        //   binder:
        //     Provides information about the deletion.
        //
        //   indexes:
        //     The indexes to be deleted.
        //
        // Returns:
        //     true if the operation is successful; otherwise, false. If this method returns
        //     false, the run-time binder of the language determines the behavior. (In most
        //     cases, a language-specific run-time exception is thrown.)
        public virtual bool TryDeleteIndex(DeleteIndexBinder binder, object[] indexes);
        //
        // Summary:
        //     Provides the implementation for operations that delete an object member. This
        //     method is not intended for use in C# or Visual Basic.
        //
        // Parameters:
        //   binder:
        //     Provides information about the deletion.
        //
        // Returns:
        //     true if the operation is successful; otherwise, false. If this method returns
        //     false, the run-time binder of the language determines the behavior. (In most
        //     cases, a language-specific run-time exception is thrown.)
        public virtual bool TryDeleteMember(DeleteMemberBinder binder);
        //
        // Summary:
        //     Provides the implementation for operations that get a value by index. Classes
        //     derived from the System.Dynamic.DynamicObject class can override this method
        //     to specify dynamic behavior for indexing operations.
        //
        // Parameters:
        //   binder:
        //     Provides information about the operation.
        //
        //   indexes:
        //     The indexes that are used in the operation. For example, for the sampleObject[3]
        //     operation in C# (sampleObject(3) in Visual Basic), where sampleObject is derived
        //     from the DynamicObject class, indexes[0] is equal to 3.
        //
        //   result:
        //     The result of the index operation.
        //
        // Returns:
        //     true if the operation is successful; otherwise, false. If this method returns
        //     false, the run-time binder of the language determines the behavior. (In most
        //     cases, a run-time exception is thrown.)
        public virtual bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result);
        //
        // Summary:
        //     Provides the implementation for operations that get member values. Classes derived
        //     from the System.Dynamic.DynamicObject class can override this method to specify
        //     dynamic behavior for operations such as getting a value for a property.
        //
        // Parameters:
        //   binder:
        //     Provides information about the object that called the dynamic operation. The
        //     binder.Name property provides the name of the member on which the dynamic operation
        //     is performed. For example, for the Console.WriteLine(sampleObject.SampleProperty)
        //     statement, where sampleObject is an instance of the class derived from the System.Dynamic.DynamicObject
        //     class, binder.Name returns "SampleProperty". The binder.IgnoreCase property specifies
        //     whether the member name is case-sensitive.
        //
        //   result:
        //     The result of the get operation. For example, if the method is called for a property,
        //     you can assign the property value to result.
        //
        // Returns:
        //     true if the operation is successful; otherwise, false. If this method returns
        //     false, the run-time binder of the language determines the behavior. (In most
        //     cases, a run-time exception is thrown.)
        public virtual bool TryGetMember(GetMemberBinder binder, out object result);
        //
        // Summary:
        //     Provides the implementation for operations that invoke an object. Classes derived
        //     from the System.Dynamic.DynamicObject class can override this method to specify
        //     dynamic behavior for operations such as invoking an object or a delegate.
        //
        // Parameters:
        //   binder:
        //     Provides information about the invoke operation.
        //
        //   args:
        //     The arguments that are passed to the object during the invoke operation. For
        //     example, for the sampleObject(100) operation, where sampleObject is derived from
        //     the System.Dynamic.DynamicObject class, args[0] is equal to 100.
        //
        //   result:
        //     The result of the object invocation.
        //
        // Returns:
        //     true if the operation is successful; otherwise, false. If this method returns
        //     false, the run-time binder of the language determines the behavior. (In most
        //     cases, a language-specific run-time exception is thrown.
        public virtual bool TryInvoke(InvokeBinder binder, object[] args, out object result);
        //
        // Summary:
        //     Provides the implementation for operations that invoke a member. Classes derived
        //     from the System.Dynamic.DynamicObject class can override this method to specify
        //     dynamic behavior for operations such as calling a method.
        //
        // Parameters:
        //   binder:
        //     Provides information about the dynamic operation. The binder.Name property provides
        //     the name of the member on which the dynamic operation is performed. For example,
        //     for the statement sampleObject.SampleMethod(100), where sampleObject is an instance
        //     of the class derived from the System.Dynamic.DynamicObject class, binder.Name
        //     returns "SampleMethod". The binder.IgnoreCase property specifies whether the
        //     member name is case-sensitive.
        //
        //   args:
        //     The arguments that are passed to the object member during the invoke operation.
        //     For example, for the statement sampleObject.SampleMethod(100), where sampleObject
        //     is derived from the System.Dynamic.DynamicObject class, args[0] is equal to 100.
        //
        //   result:
        //     The result of the member invocation.
        //
        // Returns:
        //     true if the operation is successful; otherwise, false. If this method returns
        //     false, the run-time binder of the language determines the behavior. (In most
        //     cases, a language-specific run-time exception is thrown.)
        public virtual bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result);
        //
        // Summary:
        //     Provides the implementation for operations that set a value by index. Classes
        //     derived from the System.Dynamic.DynamicObject class can override this method
        //     to specify dynamic behavior for operations that access objects by a specified
        //     index.
        //
        // Parameters:
        //   binder:
        //     Provides information about the operation.
        //
        //   indexes:
        //     The indexes that are used in the operation. For example, for the sampleObject[3]
        //     = 10 operation in C# (sampleObject(3) = 10 in Visual Basic), where sampleObject
        //     is derived from the System.Dynamic.DynamicObject class, indexes[0] is equal to
        //     3.
        //
        //   value:
        //     The value to set to the object that has the specified index. For example, for
        //     the sampleObject[3] = 10 operation in C# (sampleObject(3) = 10 in Visual Basic),
        //     where sampleObject is derived from the System.Dynamic.DynamicObject class, value
        //     is equal to 10.
        //
        // Returns:
        //     true if the operation is successful; otherwise, false. If this method returns
        //     false, the run-time binder of the language determines the behavior. (In most
        //     cases, a language-specific run-time exception is thrown.
        public virtual bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value);
        //
        // Summary:
        //     Provides the implementation for operations that set member values. Classes derived
        //     from the System.Dynamic.DynamicObject class can override this method to specify
        //     dynamic behavior for operations such as setting a value for a property.
        //
        // Parameters:
        //   binder:
        //     Provides information about the object that called the dynamic operation. The
        //     binder.Name property provides the name of the member to which the value is being
        //     assigned. For example, for the statement sampleObject.SampleProperty = "Test",
        //     where sampleObject is an instance of the class derived from the System.Dynamic.DynamicObject
        //     class, binder.Name returns "SampleProperty". The binder.IgnoreCase property specifies
        //     whether the member name is case-sensitive.
        //
        //   value:
        //     The value to set to the member. For example, for sampleObject.SampleProperty
        //     = "Test", where sampleObject is an instance of the class derived from the System.Dynamic.DynamicObject
        //     class, the value is "Test".
        //
        // Returns:
        //     true if the operation is successful; otherwise, false. If this method returns
        //     false, the run-time binder of the language determines the behavior. (In most
        //     cases, a language-specific run-time exception is thrown.)
        public virtual bool TrySetMember(SetMemberBinder binder, object value);
        //
        // Summary:
        //     Provides implementation for unary operations. Classes derived from the System.Dynamic.DynamicObject
        //     class can override this method to specify dynamic behavior for operations such
        //     as negation, increment, or decrement.
        //
        // Parameters:
        //   binder:
        //     Provides information about the unary operation. The binder.Operation property
        //     returns an System.Linq.Expressions.ExpressionType object. For example, for the
        //     negativeNumber = -number statement, where number is derived from the DynamicObject
        //     class, binder.Operation returns "Negate".
        //
        //   result:
        //     The result of the unary operation.
        //
        // Returns:
        //     true if the operation is successful; otherwise, false. If this method returns
        //     false, the run-time binder of the language determines the behavior. (In most
        //     cases, a language-specific run-time exception is thrown.)
        public virtual bool TryUnaryOperation(UnaryOperationBinder binder, out object result);
    }`enter code here`
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接