Dinamik bir değişkente bir özelliğin bulunup bulunmadığını test edin


225

Durumum çok basit. Kodumu bir yerde bu var:

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();

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

Yani, temelde sorum dinamik değişkenimde belirli bir özelliğin kullanılabilir olduğunu (istisna atmadan) nasıl kontrol edeceğim. Yapabilirdim GetType()ama nesnenin türünü bilmem gerekmediğinden bundan kaçınmayı tercih ederim. Gerçekten bilmek istediğim, bir özellik (ya da bu hayatı kolaylaştırır yöntemi) kullanılabilir olup olmadığıdır. İşaretçi var mı?


1
Burada birkaç öneri var: stackoverflow.com/questions/2985161/… - ancak şu ana kadar kabul edilmiş bir cevap yok.
Andrew Anderson

teşekkürler, köknar çözümlerden biri yapmak için nasıl görebilirsiniz, tho eksik bir şey olup olmadığını merak ediyordum
roundcrisis

Yanıtlar:


159

Bence bir dynamicDinamik bağlamanın C # derleyicisinde işlenme biçimini yeniden uygulamadığınız sürece, değişkenin erişmeye çalışmadan belirli bir üyesi . C # belirtimine göre, uygulama tanımlı olduğu için muhtemelen çok fazla tahmin içerir.

Dolayısıyla, üyeye erişmeye ve başarısız olursa bir istisna yakalamaya çalışmalısınız:

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();

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

2
Bu cevabı çok uzun olduğu için işaretleyeceğim, en iyi cevap gibi görünüyor
roundcrisis


20
@ministrymason Bunu yapmayı IDictionaryve onunla çalışmayı kastediyorsanız , bu sadece üzerinde ExpandoObjectçalışır, başka bir dynamicnesne üzerinde çalışmaz .
svick

5
RuntimeBinderExceptioniçindedir Microsoft.CSharp.RuntimeBinderad.
DavidRR

8
Hala bu senaryonun bu özelliğine bakılmaksızın genel olarak kötü bir uygulama olup olmadığı yerine try / catch kullanmak istiyorum.
Alexander Ryan Baggett

74

Martijn'in cevabı ile svick'in cevabı arasında bir karşılaştırma yapacağımı düşündüm ...

Aşağıdaki program aşağıdaki sonuçları döndürür:

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

Sonuç olarak yansıma kullanmanızı öneririm. Aşağıya bakınız.


Mülayim yorumuna cevap:

Oranlar reflection:exception100000 iterasyon için kenelerdir:

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

... yeterince adil - ~ 1 / 47'den daha düşük bir olasılıkla başarısız olmasını bekliyorsanız, istisna için gidin.


Yukarıda GetProperties()her seferinde koştuğunuz varsayılmaktadır . GetProperties()Sözlük veya benzeri her türün sonucunu önbelleğe alarak işlemi hızlandırabilirsiniz . Aynı tür kümeleri tekrar tekrar kontrol ediyorsanız bu yardımcı olabilir.


7
Katılıyorum ve uygun olduğunda işime yansımayı seviyorum. Try / Catch üzerindeki kazançları, sadece istisna atıldığında olur. Peki, burada yansımayı kullanmadan önce birisinin sorması gereken şey - belirli bir yol olması muhtemel mi? % 90 hatta% 75 zaman kodunuz geçecek mi? Sonra Try / Catch hala en uygunudur. Havadaysa veya birinin olması için çok fazla seçenek varsa, o zaman yansımanız yerinde olur.
mülayim

@bland Cevap düzenlendi.
dav_i

1
Teşekkürler, şimdi gerçekten tamamlanmış görünüyor.
mülayim

@dav_i Her ikisi de farklı davrandığından ikisini karşılaştırmak adil değil. svick'in cevabı daha eksiksiz.
nawfal

1
@dav_i Hayır, aynı işlevi yerine getirmiyorlar. Martijn'ın yanıtı, bir özelliğin dinamik olarak bildirilen (derleme zamanı güvenlik denetimlerini yoksayar) normal bir derleme zamanı türünde var olup olmadığını denetler. Oysa svick'in cevabı, bir nesnenin gerçekten dinamik bir nesne üzerinde, yani uygulayan bir şey üzerinde olup olmadığını kontrol eder . Cevabınızın ardındaki motivasyonu anlıyorum ve takdir ediyorum. Buna cevap vermek adil.IIDynamicMetaObjectProvider
nawfal

52

Belki yansıma kullanın?

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

2

Bu benim önerim ile aynı dezavantajlara sahip değil mi? RouteValueDictionary özellikleri almak için yansıma kullanır .
Steve Wilkes

12
Sadece şunları yapabilirsiniz Where:.Any(p => p.Name.Equals("PropertyName"))
dav_i

Cevap karşılaştırması için lütfen cevabıma bakın .
dav_i

3
Tek astar gibi: ((Type)myVar.GetType()).GetProperties().Any(x => x.Name.Equals("PropertyName")). Derleyicinin lambda hakkında mutlu olması için tür dökümü gerekir.
Mayıs

38

Birisine yardım etmesi durumunda:

Yöntem ise GetDataThatLooksVerySimilarButNotTheSame()bir döndürür ExpandoObjectayrıca bir yayın yapabilirsiniz IDictionaryişaretlemeden önce.

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

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

3
Bu cevabın neden daha fazla oyu olmadığından emin değilim, çünkü istenen şeyi tam olarak yapıyor (istisna atma veya yansıma yok).
Wolfshead

7
@Wolfshead Bu yanıt, dinamik nesnenizin bir ExpandoObject veya IDictionary <string, object> uygulayan başka bir şey olduğunu biliyorsanız harikadır, ancak başka bir şey olursa, bu başarısız olur.
Damian Powell

9

Bunun iki yaygın çözümü çağrı yapmak ve yakalamak RuntimeBinderException, çağrıyı kontrol etmek için yansıma kullanmak veya bir metin formatına serileştirmek ve oradan ayrıştırmaktır. İstisnalarla ilgili sorun, çok yavaş olmalarıdır, çünkü biri oluşturulduğunda, geçerli çağrı yığını serileştirilir. JSON veya benzer bir şeye seri hale getirme de benzer bir cezaya çarptırılır. Bu bizi yansıma ile terk eder, ancak yalnızca altta yatan nesne aslında üzerinde gerçek üyelere sahip bir POCO ise işe yarar. Bir sözlük, COM nesnesi veya harici bir web hizmeti etrafındaki dinamik bir sarıcıysa, yansıma yardımcı olmaz.

Diğer bir çözüm DynamicMetaObjectde, DLR'nin gördüğü gibi üye adlarını almak için kullanılır . Aşağıdaki örnekte Dynamic, Agealanı sınamak ve görüntülemek için statik bir sınıf ( ) kullanıyorum .

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

DynamiteyNuget paketinin bunu zaten yaptığı ortaya çıkıyor .
Damian Powell

8

Denis'in yanıtı JsonObjects kullanarak başka bir çözüm düşünmemi sağladı,

üstbilgi özelliği denetleyicisi:

Predicate<object> hasHeader = jsonObject =>
                                 ((JObject)jsonObject).OfType<JProperty>()
                                     .Any(prop => prop.Name == "header");

ya da belki daha iyisi:

Predicate<object> hasHeader = jsonObject =>
                                 ((JObject)jsonObject).Property("header") != null;

Örneğin:

dynamic json = JsonConvert.DeserializeObject(data);
string header = hasHeader(json) ? json.header : null;

1
Bu cevapta neyin yanlış olduğunu bilmek için bir şans var mı?
Charles HETIER

Bunun neden oylandığını bilmiyorum, benim için harika çalıştı. Her özellik için Predicate'i bir yardımcı sınıfa taşıdım ve her birinden bir bool döndürmek için Invoke yöntemini çağırdım.
markp3rry

7

Benzer bir sorunla karşılaştım ama birim testlerde.

SharpTestsEx kullanarak bir özelliğin olup olmadığını kontrol edebilirsiniz. Bu denetleyicileri kullanıyorum, çünkü JSON nesnesi dinamik olduğundan, birisi adı değiştirebilir ve javascript veya başka bir şeyde değiştirmeyi unutabilir, bu yüzden denetleyiciyi yazarken tüm özellikleri test etmek güvenliğimi arttırmalıdır.

Misal:

dynamic testedObject = new ExpandoObject();
testedObject.MyName = "I am a testing object";

Şimdi, SharTestsEx kullanarak:

Executing.This(delegate {var unused = testedObject.MyName; }).Should().NotThrow();
Executing.This(delegate {var unused = testedObject.NotExistingProperty; }).Should().Throw();

Bunu kullanarak, "gerekir (). NotThrow ()" kullanarak tüm özellikleri test.

Muhtemelen konu dışı, ama birisi için yararlı olabilir.


Teşekkürler, çok faydalı. SharpTestsEx kullanarak Ben de dinamik özelliğinin değerini test etmek için aşağıdaki satırı kullanın:((string)(testedObject.MyName)).Should().Be("I am a testing object");
Remko Jansen

2

@Karask'ın cevabından sonra, işlevi böyle bir yardımcı olarak sarabilirsiniz:

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

2

Benim için çalışıyor:

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, bu mülkün mevcut olmadığı anlamına gelmez
quetzalcoatl

Biliyorum ama null ise değeri ile hiçbir şey yapmanıza gerek yok bu yüzden benim usecase için sorun yok
Jester

0

Dinamik olarak kullanılan türü kontrol ederseniz, her özellik erişimi için bir değer yerine bir demet döndüremez misiniz? Gibi bir şey...

public class DynamicValue<T>
{
    internal DynamicValue(T value, bool exists)
    {
         Value = value;
         Exists = exists;
    }

    T Value { get; private set; }
    bool Exists { get; private set; }
}

Muhtemelen naif bir uygulama, ancak her seferinde bunlardan birini dahili olarak oluşturursanız ve bunu gerçek değer yerine döndürürseniz Exists, her mülk erişimini kontrol edebilir ve daha sonra Valuedeğerin default(T)(ve ilgisiz) değerinde olup olmadığını isabet ettirebilirsiniz .

Bununla birlikte, dinamik çalışma hakkında bazı bilgiler eksik olabilir ve bu uygulanabilir bir öneri olmayabilir.


0

Benim durumumda, belirli bir ada sahip bir yöntemin varlığını kontrol etmem gerekiyordu, bu yüzden bunun için bir arayüz kullandım

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

Ayrıca, arayüzler sadece yöntemlerden daha fazlasını içerebilir:

Arabirimler yöntemleri, özellikleri, olayları, dizinleyicileri veya bu dört üye türünün herhangi bir kombinasyonunu içerebilir.

Gönderen: Arabirimler (C # Programlama Kılavuzu)

Zarif ve istisnaları hapsetmeye veya yansımayla oynamaya gerek yok ...


0

Bu gerçekten eski bir yazı olduğunu ama burada çalışmak için basit bir çözümdür biliyoruz dynamictip c#.

  1. doğrudan özellikleri sıralamak için basit yansıma kullanabilir
  2. veya objectgenişletme yöntemini kullanabilir
  3. veya GetAsOrDefault<int>varsa değeri olan yeni veya güçlü bir nesne almak için yöntemi kullanın.
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

Gibi ExpandoObjectINHERITS IDictionary<string, object>aşağıdaki denetimini kullanabilirsiniz

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();

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

Bu kontrolü gerçekleştirmek için, kodu daha temiz ve yeniden kullanılabilir hale getirecek bir yardımcı program yapabilirsiniz.


-1

Diğer yol:

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'in özelliklerini test etmekle ilgili soruyu nereden buldunuz? Cevabınız, özellikleri üzerinde IEnumerable açığa çıkaran nesneler / sınıflarla sınırlıdır. Tarafından garanti edilmez dynamic. dynamicanahtar kelime çok daha geniş bir konudur. Eğer test eğer git çek Countde dynamic foo = new List<int>{ 1,2,3,4 }böyle
Quetzalcoatl'ın
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.