ChatGPT解决这个技术问题 Extra ChatGPT

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

我的情况很简单。在我的代码的某个地方,我有这个:

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();

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

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

这里有几个建议:stackoverflow.com/questions/2985161/… - 但到目前为止没有接受的答案。
谢谢,我可以看到如何制作解决方案之一,但我想知道是否有什么我遗漏的

s
svick

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

因此,如果失败,您实际上应该尝试访问该成员并捕获异常:

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();

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

我会将此标记为答案,因为它已经很久了,它似乎是最好的答案
更好的解决方案 - stackoverflow.com/questions/2839598/…
@ministrymason 如果您的意思是投射到 IDictionary 并使用它,它只适用于 ExpandoObject,它不适用于任何其他 dynamic 对象。
RuntimeBinderException 位于 Microsoft.CSharp.RuntimeBinder 命名空间中。
无论这种情况的具体情况如何,我仍然觉得使用 try/catch 而不是 if/else 通常是不好的做法。
C
Community

我想我会比较 Martijn's answersvick'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() 的结果来加快该过程。如果您一遍又一遍地检查同一组类型,这可能会有所帮助。


我同意并喜欢在适当的时候反思我的工作。它对 Try/Catch 的好处仅在于抛出异常时。那么在这里使用反射之前有人应该问什么 - 它可能是某种方式吗? 90% 甚至 75% 的时间,你的代码会通过吗?那么 Try/Catch 仍然是最优的。如果它悬而未决,或者一个选择太多,最有可能,那么你的反思是正确的。
@bland 编辑答案。
谢谢,现在看起来真的很完整。
@dav_i 比较两者是不公平的,因为两者的行为不同。 svick 的回答更完整。
@dav_i 不,它们不执行相同的功能。 Martijn 的答案检查 C# 中的常规编译时类型 上是否存在属性,即声明的动态(意味着它忽略编译时安全检查)。而 svick 的答案检查属性是否存在于 真正动态 对象上,即实现 IIDynamicMetaObjectProvider 的东西。我确实理解您回答背后的动机,并对此表示感谢。这么回答是公平的。
M
Martijn

也许使用反射?

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

引用问题“。我可以做 GetType() 但我宁愿避免这样做”
这与我的建议没有相同的缺点吗? RouteValueDictionary uses reflection to get properties
您可以不使用 Where.Any(p => p.Name.Equals("PropertyName"))
Please see my answer 用于答案比较。
作为单行:((Type)myVar.GetType()).GetProperties().Any(x => x.Name.Equals("PropertyName"))。需要强制转换为类型才能使编译器对 lambda 感到满意。
k
karask

以防万一它可以帮助某人:

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

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

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

不知道为什么这个答案没有更多的选票,因为它完全符合要求(没有异常抛出或反射)。
@Wolfshead 如果您知道动态对象是 ExpandoObject 或实现 IDictionary 的其他东西,那么这个答案很好,但如果它碰巧是其他东西,那么这将失败。
I
Ian Kemp

对此的两种常见解决方案包括进行调用并捕获 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)
不工作,在 VS2022 上测试,GetMembersName 返回具有许多属性的空解析对象
q
quetzalcoatl

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;

请问有没有机会知道这个答案有什么问题?
不知道为什么这被否决了,对我来说效果很好。我将每个属性的 Predicate 移动到一个辅助类中,并调用 Invoke 方法从每个属性中返回一个布尔值。
@CharlesHETIER 因为这个问题与 JSON 无关。
@IanKemp 我认为您错过了此处代码的目的。这种用 Json API 解决问题的特殊想法可能不是自然的方法,但它确实提供了一种解决问题的方法。
D
Diego Santin

好吧,我遇到了类似的问题,但在单元测试中。

使用 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()”测试所有现有属性。

它可能超出主题,但对某人有用。


谢谢,很有用。使用 SharpTestsEx 我还使用以下行来测试动态属性的值:((string)(testedObject.MyName)).Should().Be("I am a testing object");
W
Wolfshead

继@karask 的回答之后,您可以像这样将函数包装为助手:

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

J
Junek

对我来说,这有效:

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 并不表示该属性不存在
我知道,但如果它是 null 我不需要对这个值做任何事情,因此对于我的用例来说没关系
S
Shibumi

如果您控制用作动态的类型,您不能为每个属性访问返回一个元组而不是一个值吗?就像是...

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(并且无关紧要)如果不是。

也就是说,我可能缺少一些关于动态如何工作的知识,这可能不是一个可行的建议。


V
Vaccano

这是另一种方式:

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;
            }
        }
    }
}

您从哪里得到关于测试 JObject 属性的问题的想法?您的答案仅限于在其属性上公开 IEnumerable 的对象/类。 dynamic 不保证。 dynamic 关键字是更多更广泛的主题。去检查您是否可以像这样在 dynamic foo = new List<int>{ 1,2,3,4 } 中测试 Count
F
Fred Mauroy

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

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

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

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

来自:Interfaces (C# Programming Guide)

优雅,无需捕获异常或玩反射......


S
SimperT

我知道这是很老的帖子,但这里有一个简单的解决方案,可以在 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 );
    }
}

M
Mukesh Bhojwani

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

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();

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

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


我认为这里 stackoverflow.com/a/29693414(以及评论中的潜在问题)和这里 stackoverflow.com/a/37938989 的其他答案已经涵盖了这一点