我的情况很简单。在我的代码的某个地方,我有这个:
dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();
//How to do this?
if (myVariable.MyProperty.Exists)
//Do stuff
所以,基本上我的问题是如何检查(不抛出异常)某个属性在我的动态变量上是否可用。我可以做 GetType()
但我宁愿避免这样做,因为我真的不需要知道对象的类型。我真正想知道的是一个属性(或方法,如果这让生活更轻松的话)是否可用。任何指针?
我认为没有办法找出 dynamic
变量是否具有某个成员而不尝试访问它,除非您重新实现了在 C# 编译器中处理动态绑定的方式。根据 C# 规范,这可能包括很多猜测,因为它是实现定义的。
因此,如果失败,您实际上应该尝试访问该成员并捕获异常:
dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();
try
{
var x = myVariable.MyProperty;
// do stuff with x
}
catch (RuntimeBinderException)
{
// MyProperty doesn't exist
}
我想我会比较 Martijn's answer 和 svick's answer...
以下程序返回以下结果:
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"));
}
As a result I'd suggest using reflection. 见下文。
回应平淡无奇的评论:
比率是 100000 次迭代的 reflection:exception
个刻度:
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()
的结果来加快该过程。如果您一遍又一遍地检查同一组类型,这可能会有所帮助。
IIDynamicMetaObjectProvider
的东西。我确实理解您回答背后的动机,并对此表示感谢。这么回答是公平的。
也许使用反射?
dynamic myVar = GetDataThatLooksVerySimilarButNotTheSame();
Type typeOfDynamic = myVar.GetType();
bool exist = typeOfDynamic.GetProperties().Where(p => p.Name.Equals("PropertyName")).Any();
Where
:.Any(p => p.Name.Equals("PropertyName"))
((Type)myVar.GetType()).GetProperties().Any(x => x.Name.Equals("PropertyName"))
。需要强制转换为类型才能使编译器对 lambda 感到满意。
以防万一它可以帮助某人:
如果方法 GetDataThatLooksVerySimilarButNotTheSame()
返回 ExpandoObject
,您也可以在检查之前强制转换为 IDictionary
。
dynamic test = new System.Dynamic.ExpandoObject();
test.foo = "bar";
if (((IDictionary<string, object>)test).ContainsKey("foo"))
{
Console.WriteLine(test.foo);
}
对此的两种常见解决方案包括进行调用并捕获 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;
}
}
Dynamitey
nuget 包已经这样做了。 (nuget.org/packages/Dynamitey)
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;
好吧,我遇到了类似的问题,但在单元测试中。
使用 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()”测试所有现有属性。
它可能超出主题,但对某人有用。
((string)(testedObject.MyName)).Should().Be("I am a testing object");
继@karask 的回答之后,您可以像这样将函数包装为助手:
public static bool HasProperty(ExpandoObject expandoObj,
string name)
{
return ((IDictionary<string, object>)expandoObj).ContainsKey(name);
}
对我来说,这有效:
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;
}
}
null
并不表示该属性不存在
如果您控制用作动态的类型,您不能为每个属性访问返回一个元组而不是一个值吗?就像是...
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
,然后如果它的值为 default(T)
,则点击 Value
(并且无关紧要)如果不是。
也就是说,我可能缺少一些关于动态如何工作的知识,这可能不是一个可行的建议。
这是另一种方式:
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;
}
}
}
}
dynamic
不保证。 dynamic
关键字是更多更广泛的主题。去检查您是否可以像这样在 dynamic foo = new List<int>{ 1,2,3,4 }
中测试 Count
就我而言,我需要检查是否存在具有特定名称的方法,因此我为此使用了一个接口
var plugin = this.pluginFinder.GetPluginIfInstalled<IPlugin>(pluginName) as dynamic;
if (plugin != null && plugin is ICustomPluginAction)
{
plugin.CustomPluginAction(action);
}
此外,接口可以包含的不仅仅是方法:
接口可以包含方法、属性、事件、索引器或这四种成员类型的任意组合。
来自:Interfaces (C# Programming Guide)
优雅,无需捕获异常或玩反射......
我知道这是很老的帖子,但这里有一个简单的解决方案,可以在 c#
中使用 dynamic
类型。
可以使用简单的反射来枚举直接属性,也可以使用对象扩展方法或使用 GetAsOrDefault
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
您可以创建一个实用方法来执行此检查,这将使代码更加清晰和可重用
不定期副业成功案例分享
IDictionary
并使用它,它只适用于ExpandoObject
,它不适用于任何其他dynamic
对象。RuntimeBinderException
位于Microsoft.CSharp.RuntimeBinder
命名空间中。