동적 변수에서 속성을 사용할 수 있는지 테스트


225

내 상황은 매우 간단합니다. 내 코드 어딘가에 나는 이것을 가지고있다 :

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();

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

따라서 기본적으로 내 질문은 특정 속성이 내 동적 변수에서 사용 가능한지 확인하는 방법입니다 (예외 발생). 나는 할 수 GetType()있지만 객체의 유형을 알 필요가 없기 때문에 오히려 피하고 싶습니다. 내가 정말로 알고 싶은 것은 재산 (또는 삶을 더 쉽게 만드는 방법)이 있는지 여부입니다. 어떤 포인터?


1
여기에 몇 가지 제안이 있습니다 : stackoverflow.com/questions/2985161/…- 그러나 지금까지는 대답이 없습니다.
앤드류 앤더슨

궁금 그래도 내가 밖으로 실종 만약 거기에 아무것도 덕분에, 나는, 솔루션의 전나무 하나를 만드는 방법을 볼 수 있습니다
roundcrisis

답변:


159

dynamicC # 컴파일러에서 동적 바인딩이 처리되는 방식을 다시 구현하지 않으면 변수에 액세스하지 않고 특정 멤버가 있는지 여부를 알 수있는 방법이 없다고 생각합니다 . C # 사양에 따라 구현 정의되어 있기 때문에 많은 추측이 포함될 것입니다.

따라서 실제로 멤버에 액세스하고 실패하면 예외를 포착해야합니다.

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();

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

2
이 답변을 너무 오랫동안 답변으로 표시하겠습니다. 가장 좋은 답변 인 것 같습니다
roundcrisis


20
@ministrymason 만약 당신이 그것으로 캐스팅 IDictionary하고 작업하는 것을 의미한다면 ExpandoObject, 그것은 오직 다른 dynamic객체 에서만 작동 하지 않을 것입니다 .
svick

5
RuntimeBinderExceptionMicrosoft.CSharp.RuntimeBinder네임 스페이스.
DavidRR

8
이 시나리오의 특정 사항에 관계없이 일반적으로 if / else 대신 일반적으로 나쁜 습관 대신 try / catch를 사용하고 싶습니다.
Alexander Ryan Baggett

74

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의 의견에 답변 :

비율은 reflection:exception100000 회 반복에 대한 틱입니다.

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()사전 또는 이와 유사한 방식으로 각 유형 의 결과를 캐싱하여 프로세스 속도를 높일 수 있습니다 . 동일한 유형의 집합을 반복해서 확인하는 경우 도움이 될 수 있습니다.


7
적절한 경우 내 업무에 반영하고 동의합니다. Try / Catch에 비해 이득은 예외가 발생할 때만 발생합니다. 그래서 여기에서 리플렉션을 사용하기 전에 누군가가 무엇을 요구해야합니까? 시간의 90 % 또는 75 %, 코드가 통과됩니까? 그런 다음 Try / Catch는 여전히 최적입니다. 공중에 올라가거나 가장 많은 선택을 할 수 있다면, 당신의 반성이 나타납니다.
bland

@bland 수정 된 답변.
dav_i

1
고마워요, 이제 정말 완성되었습니다.
bland

@dav_i 둘 다 다르게 행동하기 때문에 둘 다를 비교하는 것은 불공평합니다. svick의 답변이 더 완전합니다.
nawfal

1
@dav_i 아니요, 동일한 기능을 수행하지 않습니다. Martijn의 답변 은 C # 의 정규 컴파일 시간 유형 에 속성이 존재하는지 확인합니다. 이는 동적 으로 선언됩니다 (컴파일 시간 안전 확인을 무시 함). svick의 답변은 속성이 진정한 동적 객체, 즉 구현하는 객체 에 존재하는지 확인합니다 IIDynamicMetaObjectProvider. 나는 당신의 대답 뒤에 동기를 이해하고 감사합니다. 그렇게 대답하는 것이 공평합니다.
nawfal

52

아마도 반사를 사용합니까?

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

2
질문에서 인용 ". GetType () 할 수 있지만 오히려 피할 것"
roundcrisis

이것은 내 제안과 같은 단점이 있습니까? RouteValueDictionary는 리플렉션을 사용하여 속성을 가져옵니다 .
Steve Wilkes

12
당신은 그냥없이 할 수 있습니다 Where:.Any(p => p.Name.Equals("PropertyName"))
dav_i


3
단일 라이너로 : ((Type)myVar.GetType()).GetProperties().Any(x => x.Name.Equals("PropertyName")). 컴파일러가 람다에 대해 만족하게하려면 유형으로 캐스트해야합니다.
MushinNoShin

38

누군가를 돕는 경우를 대비하여 :

메소드가를 GetDataThatLooksVerySimilarButNotTheSame()반환하면 확인하기 전에 ExpandoObject캐스팅 할 수도 있습니다 IDictionary.

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

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

3
이 답변에 왜 더 많은 투표권이 없는지 잘 모르겠습니다. 왜냐하면 요청 된 내용 (예외 처리 또는 반영 없음)을 정확하게 수행하기 때문입니다.
Wolfshead

7
@Wolfshead이 대답은 동적 객체가 ExpandoObject 또는 IDictionary <string, object>를 구현하는 다른 객체라는 것을 알고 있으면 좋지만 다른 경우에는 실패합니다.
Damian Powell

9

이에 대한 두 가지 일반적인 해결책은 전화 걸기 및 받기, RuntimeBinderException리플렉션을 사용하여 전화 확인 또는 텍스트 형식으로 직렬화 및 구문 분석을 포함합니다. 예외의 문제점은 하나가 구성 될 때 현재 호출 스택이 직렬화되기 때문에 매우 느리다는 것입니다. JSON 또는 유사한 것으로 직렬화하면 비슷한 형벌이 발생합니다. 이것은 우리에게 반성을 남기지 만 기본 객체가 실제로 실제 멤버가있는 POCO 인 경우에만 작동합니다. 사전, COM 개체 또는 외부 웹 서비스 주변의 동적 래퍼 인 경우 리플렉션이 도움이되지 않습니다.

또 다른 해결책 DynamicMetaObject은를 사용하여 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.org/packages/Dynamitey )
Damian Powell

8

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;

1
이 답변에 어떤 문제가 있는지 알 수있는 기회가 있습니까?
Charles HETIER

왜 이것이 투표를했는지 모르고 저에게 큰 도움이되었습니다. 각 속성의 Predicate를 도우미 클래스로 옮기고 각각의 부울을 반환하기 위해 Invoke 메서드를 호출했습니다.
markp3rry

7

글쎄, 나는 비슷한 문제에 직면했지만 단위 테스트에 직면했다.

SharpTestsEx를 사용하면 속성이 존재하는지 확인할 수 있습니다. JSON 객체가 동적이기 때문에 누군가가 이름을 변경하고 자바 스크립트 등에서 이름을 변경하는 것을 잊어 버릴 수 있으므로이 컨트롤러 테스트를 사용합니다. 따라서 컨트롤러를 작성할 때 모든 속성을 테스트하면 안전성이 높아집니다.

예:

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");
Remko Jansen

2

@karask의 답변에서 다음과 같이 함수를 도우미로 래핑 할 수 있습니다.

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

2

나를 위해 이것은 작동합니다 :

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이라면 나는 그 값으로 아무것도 할 필요가 없으므로 내 유스 케이스에 대해서는 괜찮다
Jester

0

동적으로 사용되는 유형을 제어하면 모든 속성 액세스에 대한 값 대신 튜플을 반환 할 수 없습니까? 뭔가 ...

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) 이 아닌 경우 관련되지 않은 경우 있습니다.

즉, 동적 작동 방식에 대한 약간의 지식이 누락되었을 수 있으며 이는 실용적인 제안이 아닐 수 있습니다.


0

필자의 경우 특정 이름의 메소드가 있는지 확인해야하므로 인터페이스를 사용했습니다.

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

또한 인터페이스는 단순한 메소드 이상의 것을 포함 할 수 있습니다.

인터페이스에는 메서드, 속성, 이벤트, 인덱서 또는 이러한 4 가지 멤버 유형의 조합이 포함될 수 있습니다.

보낸 사람 : 인터페이스 (C # 프로그래밍 가이드)

우아하고 예외를 잡거나 반성을 가지고 놀 필요가 없습니다 ...


0

나는 이것이 정말로 오래된 게시물이라는 것을 알고 있지만 여기에 dynamictype 을 사용하는 간단한 솔루션이 있습니다 c#.

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

0

으로 ExpandoObject상속하는 IDictionary<string, object>다음과 같은 검사를 사용할 수 있습니다

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();

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

이 검사를 수행하는 유틸리티 메소드를 작성하면 코드를 훨씬 깨끗하고 재사용 할 수 있습니다.


-1

다른 방법은 다음과 같습니다.

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

2
JObject의 속성을 테스트하는 것에 대한 질문을 어디에서 얻었습니까? 귀하의 답변은 IEnumerable을 속성에 노출시키는 객체 / 클래스로 제한됩니다. 에 의해 보장되지는 않습니다 dynamic. dynamic키워드가 넓은 주제입니다. 당신이 테스트 할 수있는 경우 이동 체크 Countdynamic foo = new List<int>{ 1,2,3,4 }같은
케찰코아틀
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.