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

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

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();

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

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



답변

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

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

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();

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


답변

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


답변

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

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


답변

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

메소드가를 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 개체 또는 외부 웹 서비스 주변의 동적 래퍼 인 경우 리플렉션이 도움이되지 않습니다.

또 다른 해결책 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;
    }
}


답변

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 객체가 동적이기 때문에 누군가가 이름을 변경하고 자바 스크립트 등에서 이름을 변경하는 것을 잊어 버릴 수 있으므로이 컨트롤러 테스트를 사용합니다. 따라서 컨트롤러를 작성할 때 모든 속성을 테스트하면 안전성이 높아집니다.

예:

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 ()”를 사용하여 기존의 모든 속성을 테스트합니다.

아마도 주제가 아닐 수도 있지만 누군가에게 유용 할 수 있습니다.