我的情况很简单。在我的代码中,我有这样的代码:
dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();
//How to do this?
if (myVariable.MyProperty.Exists)
//Do stuff
因此,基本上我的问题是如何检查(不抛出异常)某个属性在我的动态变量上是可用的。我可以使用GetType(),但我宁愿避免这样做,因为我并不真正需要知道对象的类型。我真正想知道的是是否有一个属性(或方法,如果这使工作更简单的话)可用。指针吗?
如果你控制的类型被用作动态,你不能返回一个元组而不是每个属性访问的值吗?之类的……
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,如果值为默认值(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()”。
这可能离题了,但对某人来说可能有用。
我想我应该比较一下Martijn和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次迭代的异常滴答声:
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()结果缓存到字典或类似内容中来加快该过程。如果您要反复检查同一组类型,这可能会有所帮助。
下面是另一种方法:
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;
}
}
}
}
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;
The two common solutions to this include making the call and catching the RuntimeBinderException, using reflection to check for the call, or serialising to a text format and parsing from there. The problem with exceptions is that they are very slow, because when one is constructed, the current call stack is serialised. Serialising to JSON or something analogous incurs a similar penalty. This leaves us with reflection but it only works if the underlying object is actually a POCO with real members on it. If it's a dynamic wrapper around a dictionary, a COM object, or an external web service, then reflection won't help.
另一种解决方案是使用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;
}
}
我知道这是一个很老的帖子,但这里有一个简单的解决方案,在c#中使用动态类型。
可以使用简单的反射来枚举直接属性吗
或者可以使用对象扩展的方法
或使用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 );
}
}