测试动态变量上的属性是否可用

我的情况很简单。在我的代码中,我有这样的代码:

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();


//How to do this?
if (myVariable.MyProperty.Exists)
//Do stuff

因此,基本上我的问题是如何检查(不抛出异常)某个属性在我的动态变量上是可用的。我可以做GetType(),但我宁愿避免这样做,因为我并不真的需要知道对象的类型。我真正想知道的是是否有一个属性(或方法,如果这使工作更简单的话)可用。指针吗?

155876 次浏览

我认为没有办法在不试图访问它的情况下找出dynamic变量是否有某个成员,除非你在c#编译器中重新实现动态绑定的处理方式。这可能包含很多猜测,因为根据c#规范,它是由实现定义的。

所以你应该尝试访问成员并捕获异常,如果它失败了:

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();


try
{
var x = myVariable.MyProperty;
// do stuff with x
}
catch (RuntimeBinderException)
{
//  MyProperty doesn't exist
}

也许使用反射?

dynamic myVar = GetDataThatLooksVerySimilarButNotTheSame();
Type typeOfDynamic = myVar.GetType();
bool exist = typeOfDynamic.GetProperties().Where(p => p.Name.Equals("PropertyName")).Any();

如果你控制的类型被用作动态,你不能返回一个元组而不是每个属性访问的值吗?之类的……

public class DynamicValue<T>
{
internal DynamicValue(T value, bool exists)
{
Value = value;
Exists = exists;
}


T Value { get; private set; }
bool Exists { get; private set; }
}

这可能是一个简单的实现,但如果你每次都在内部构造一个这样的实现,并返回它而不是实际值,你可以在每次属性访问时检查Exists,然后如果它是Value,如果它不是default(T)(并且不相关),则单击Value

也就是说,我可能会遗漏一些动态工作原理的知识,这可能不是一个可行的建议。

我在单元测试中遇到过类似的问题。

使用SharpTestsEx可以检查属性是否存在。我使用这个测试我的控制器,因为JSON对象是动态的,有人可以更改名称,而忘记在javascript中更改它或其他东西,所以在编写控制器时测试所有属性应该增加我的安全性。

例子:

dynamic testedObject = new ExpandoObject();
testedObject.MyName = "I am a testing object";

现在,使用SharTestsEx:

Executing.This(delegate {var unused = testedObject.MyName; }).Should().NotThrow();
Executing.This(delegate {var unused = testedObject.NotExistingProperty; }).Should().Throw();

使用这个,我测试所有现有的属性使用“Should(). notthrow()”。

这可能离题了,但对某人来说可能有用。

我想我应该做一个比较卡坦的回答svick的回答

下面的程序返回以下结果:

Testing with exception: 2430985 ticks
Testing with reflection: 155570 ticks

void Main()
{
var random = new Random(Environment.TickCount);


dynamic test = new Test();


var sw = new Stopwatch();


sw.Start();


for (int i = 0; i < 100000; i++)
{
TestWithException(test, FlipCoin(random));
}


sw.Stop();


Console.WriteLine("Testing with exception: " + sw.ElapsedTicks.ToString() + " ticks");


sw.Restart();


for (int i = 0; i < 100000; i++)
{
TestWithReflection(test, FlipCoin(random));
}


sw.Stop();


Console.WriteLine("Testing with reflection: " + sw.ElapsedTicks.ToString() + " ticks");
}


class Test
{
public bool Exists { get { return true; } }
}


bool FlipCoin(Random random)
{
return random.Next(2) == 0;
}


bool TestWithException(dynamic d, bool useExisting)
{
try
{
bool result = useExisting ? d.Exists : d.DoesntExist;
return true;
}
catch (Exception)
{
return false;
}
}


bool TestWithReflection(dynamic d, bool useExisting)
{
Type type = d.GetType();


return type.GetProperties().Any(p => p.Name.Equals(useExisting ? "Exists" : "DoesntExist"));
}

因此我建议使用反射见下文。


回应bland的评论:

100000次迭代的比率为reflection:exception tick:

Fails 1/1: - 1:43 ticks
Fails 1/2: - 1:22 ticks
Fails 1/3: - 1:14 ticks
Fails 1/5: - 1:9 ticks
Fails 1/7: - 1:7 ticks
Fails 1/13: - 1:4 ticks
Fails 1/17: - 1:3 ticks
Fails 1/23: - 1:2 ticks
...
Fails 1/43: - 1:2 ticks
Fails 1/47: - 1:1 ticks

...很公平——如果你认为它失败的概率小于~1/47,那么就去找例外。


上面假设你每次都运行GetProperties()。你可以通过将每种类型的GetProperties()的结果缓存到字典或类似的东西中来加速这个过程。如果您要反复检查同一组类型,这可能会有所帮助。

对我来说,这是可行的:

if (IsProperty(() => DynamicObject.MyProperty))
; // do stuff






delegate string GetValueDelegate();


private bool IsProperty(GetValueDelegate getValueMethod)
{
try
{
//we're not interesting in the return value.
//What we need to know is whether an exception occurred or not


var v = getValueMethod();
return v != null;
}
catch (RuntimeBinderException)
{
return false;
}
catch
{
return true;
}
}

下面是另一种方法:

using Newtonsoft.Json.Linq;


internal class DymanicTest
{
public static string Json = @"{
""AED"": 3.672825,
""AFN"": 56.982875,
""ALL"": 110.252599,
""AMD"": 408.222002,
""ANG"": 1.78704,
""AOA"": 98.192249,
""ARS"": 8.44469
}";


public static void Run()
{
dynamic dynamicObject = JObject.Parse(Json);


foreach (JProperty variable in dynamicObject)
{
if (variable.Name == "AMD")
{
var value = variable.Value;
}
}
}
}

以防它能帮到别人:

如果方法GetDataThatLooksVerySimilarButNotTheSame()返回ExpandoObject,你也可以在检查之前强制转换为IDictionary

dynamic test = new System.Dynamic.ExpandoObject();
test.foo = "bar";


if (((IDictionary<string, object>)test).ContainsKey("foo"))
{
Console.WriteLine(test.foo);
}

Denis的回答让我想到了另一个使用JsonObjects的解决方案,

头属性检查器:

Predicate<object> hasHeader = jsonObject =>
((JObject)jsonObject).OfType<JProperty>()
.Any(prop => prop.Name == "header");

或许更好:

Predicate<object> hasHeader = jsonObject =>
((JObject)jsonObject).Property("header") != null;

例如:

dynamic json = JsonConvert.DeserializeObject(data);
string header = hasHeader(json) ? json.header : null;

根据@karask的回答,你可以像这样将函数包装为一个helper:

public static bool HasProperty(ExpandoObject expandoObj,
string name)
{
return ((IDictionary<string, object>)expandoObj).ContainsKey(name);
}

两种常见的解决方案包括调用和捕获RuntimeBinderException,使用反射检查调用,或序列化为文本格式并从那里解析。异常的问题是它们非常慢,因为当构造一个异常时,当前的调用堆栈是序列化的。序列化到JSON或类似的东西会导致类似的惩罚。这给我们留下了反射,但只有当底层对象实际上是一个具有真实成员的POCO时,它才有效。如果它是一个字典、COM对象或外部web服务的动态包装器,那么反射将不起作用。

另一个解决方案是使用IDynamicMetaObjectProvider来获取DLR看到的成员名。在下例中,我使用一个静态类(Dynamic)来测试Age字段并显示它。

class Program
{
static void Main()
{
dynamic x = new ExpandoObject();


x.Name = "Damian Powell";
x.Age = "21 (probably)";


if (Dynamic.HasMember(x, "Age"))
{
Console.WriteLine("Age={0}", x.Age);
}
}
}


public static class Dynamic
{
public static bool HasMember(object dynObj, string memberName)
{
return GetMemberNames(dynObj).Contains(memberName);
}


public static IEnumerable<string> GetMemberNames(object dynObj)
{
var metaObjProvider = dynObj as IDynamicMetaObjectProvider;


if (null == metaObjProvider) throw new InvalidOperationException(
"The supplied object must be a dynamic object " +
"(i.e. it must implement IDynamicMetaObjectProvider)"
);


var metaObj = metaObjProvider.GetMetaObject(
Expression.Constant(metaObjProvider)
);


var memberNames = metaObj.GetDynamicMemberNames();


return memberNames;
}
}

在我的例子中,我需要检查具有特定名称的方法是否存在,因此我使用了一个接口

var plugin = this.pluginFinder.GetPluginIfInstalled<IPlugin>(pluginName) as dynamic;
if (plugin != null && plugin is ICustomPluginAction)
{
plugin.CustomPluginAction(action);
}

此外,接口可以包含的不仅仅是方法:

接口可以包含方法、属性、事件、索引器或任何 这四种成员类型的组合

来自:接口(c#编程指南)

优雅,不需要陷阱异常或发挥反射…

我知道这是一个很老的帖子,但这里有一个简单的解决方案,在c#中使用dynamic类型。

  1. 可以使用简单的反射来枚举直接属性吗
  2. 或者可以使用object扩展方法
  3. 或使用GetAsOrDefault<int>方法获取一个新的强类型对象,如果存在则带值,如果不存在则默认。
public static class DynamicHelper
{
private static void Test( )
{
dynamic myobj = new
{
myInt = 1,
myArray = new[ ]
{
1, 2.3
},
myDict = new
{
myInt = 1
}
};


var myIntOrZero = myobj.GetAsOrDefault< int >( ( Func< int > )( ( ) => myobj.noExist ) );
int? myNullableInt = GetAs< int >( myobj, ( Func< int > )( ( ) => myobj.myInt ) );


if( default( int ) != myIntOrZero )
Console.WriteLine( $"myInt: '{myIntOrZero}'" );


if( default( int? ) != myNullableInt )
Console.WriteLine( $"myInt: '{myNullableInt}'" );


if( DoesPropertyExist( myobj, "myInt" ) )
Console.WriteLine( $"myInt exists and it is: '{( int )myobj.myInt}'" );
}


public static bool DoesPropertyExist( dynamic dyn, string property )
{
var t = ( Type )dyn.GetType( );
var props = t.GetProperties( );
return props.Any( p => p.Name.Equals( property ) );
}


public static object GetAs< T >( dynamic obj, Func< T > lookup )
{
try
{
var val = lookup( );
return ( T )val;
}
catch( RuntimeBinderException ) { }


return null;
}


public static T GetAsOrDefault< T >( this object obj, Func< T > test )
{
try
{
var val = test( );
return ( T )val;
}
catch( RuntimeBinderException ) { }


return default( T );
}
}

由于ExpandoObject继承了IDictionary<string, object>,您可以使用以下检查

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();


if (((IDictionary<string, object>)myVariable).ContainsKey("MyProperty"))
//Do stuff

您可以创建一个实用程序方法来执行此检查,这将使代码更加清晰和可重用

如果你的用例是转换一个api响应,只携带一些字段,你可以使用这个:

var template = new { address = new { street = "" } };
var response = JsonConvert.DeserializeAnonymousType(await result.Content.ReadAsStringAsync(), template);


string street = response?.address?.street;