Özel bir yöntemi çağırmak için yansımayı nasıl kullanabilirim?


326

Sınıfımda bir grup özel yöntem vardır ve bir girdi değerine göre dinamik olarak çağırmak gerekir. Hem çağrı kodu hem de hedef yöntemler aynı örnektir. Kod şöyle görünür:

MethodInfo dynMethod = this.GetType().GetMethod("Draw_" + itemType);
dynMethod.Invoke(this, new object[] { methodParams });

Bu durumda, GetMethod()özel yöntemler döndürmez. Özel yöntemleri bulabilmesi BindingFlagsiçin nelere ihtiyacım var GetMethod()?

Yanıtlar:


498

BindingFlags'ı kabul eden aşırı yüklenmiş sürümünüGetMethod kullanmak için kodunuzu değiştirmeniz yeterlidir :

MethodInfo dynMethod = this.GetType().GetMethod("Draw_" + itemType, 
    BindingFlags.NonPublic | BindingFlags.Instance);
dynMethod.Invoke(this, new object[] { methodParams });

İşte BindingFlags numaralandırma belgeleri .


248
Bu konuda kendimi çok sıkıntıya sokacağım.
Frank Schwieterman

1
BindingFlags.NonPublicdönen privateyöntem değil .. :(
Moumit

4
@MoumitMondal yöntemleriniz statik mi? Statik olmayan yöntemlerin BindingFlags.Instanceyanı sıra belirtmeniz gerekir BindingFlags.NonPublic.
BrianS

Hayır @BrianS .. yöntemi non-staticve privateve sınıf devralındı System.Web.UI.Page.. yine de beni aptal yapmak .. nedenini bulamadım .. :(
Moumit

3
Ekleme BindingFlags.FlattenHierarchy, üst sınıflardan örneğinize yöntemler almanıza olanak tanır.
Dragonthoughts

67

BindingFlags.NonPublicherhangi bir sonucu tek başına döndürmez. Anlaşıldığı gibi, onu birleştirmek BindingFlags.Instancehile yapar.

MethodInfo dynMethod = this.GetType().GetMethod("Draw_" + itemType, 
    BindingFlags.NonPublic | BindingFlags.Instance);

Aynı mantık internalişlevler için de geçerlidir
supertopi

Benzer bir sorunum var. "Bu" bir alt sınıfsa ve ebeveynin özel yöntemini çağırmaya çalışırsanız ne olur?
PersianLife

Base.base sınıfı korumalı yöntemleri çağırmak için bunu kullanmak mümkün müdür?
Shiv

51

Ve gerçekten başınızı belaya sokmak istiyorsanız, bir uzantı yöntemi yazarak yürütmeyi kolaylaştırın:

static class AccessExtensions
{
    public static object call(this object o, string methodName, params object[] args)
    {
        var mi = o.GetType ().GetMethod (methodName, System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance );
        if (mi != null) {
            return mi.Invoke (o, args);
        }
        return null;
    }
}

Ve kullanım:

    class Counter
    {
        public int count { get; private set; }
        void incr(int value) { count += value; }
    }

    [Test]
    public void making_questionable_life_choices()
    {
        Counter c = new Counter ();
        c.call ("incr", 2);             // "incr" is private !
        c.call ("incr", 3);
        Assert.AreEqual (5, c.count);
    }

14
Tehlikeli? Evet. Ama birim test ad alanım içine sarıldığında büyük bir yardımcı uzantısı. Bunun için teşekkürler.
Robert Wahler

5
Eğer denilen yöntemden atılan gerçek istisnalar önemsiyorsanız, hedef blok yakalamak ve TargetInvokationException yakalandığında yerine iç istisna atmak deneyin iyi bir fikirdir. Bunu birim test yardımcısı uzantımda yapıyorum.
Slobodan Savkovic

2
Yansıma tehlikeli mi? Hmmm ... C #, Java, Python ... aslında her şey tehlikeli, hatta dünya bile: D Sadece nasıl güvenli bir şekilde yapılacağına dikkat etmelisin ...
Efsaneler

16

Microsoft kısa bir süre önce yansıma API'sini değiştirerek bu yanıtların çoğunu eski haline getirdi. Aşağıdakiler modern platformlarda (Xamarin.Forms ve UWP dahil) çalışmalıdır:

obj.GetType().GetTypeInfo().GetDeclaredMethod("MethodName").Invoke(obj, yourArgsHere);

Veya bir uzantı yöntemi olarak:

public static object InvokeMethod<T>(this T obj, string methodName, params object[] args)
{
    var type = typeof(T);
    var method = type.GetTypeInfo().GetDeclaredMethod(methodName);
    return method.Invoke(obj, args);
}

Not:

  • İstenen yöntem bir üst sınıfta ise jenerik açıkça üst sınıf türüne ayarlanmalıdır.objT

  • Yöntem eşzamansızsa kullanabilirsiniz await (Task) obj.InvokeMethod(…).


En azından UWP .net sürümü için çalışmaz, çünkü yalnızca genel yöntemler için çalışır : " Geçerli adda belirtilen adla eşleşen tüm genel yöntemleri içeren bir koleksiyon döndürür ".
Dmytro Bondarenko

1
@DmytroBondarenko Özel yöntemlere karşı test ettim ve işe yaradı. Ancak bunu gördüm. Neden dokümantasyondan farklı davrandığından emin değilim, ama en azından işe yarıyor.
Owen James

Dokümantasyonun GetDeclareMethod()sadece herkese açık bir yöntemi almak için kullanılması amaçlanıyorsa , evet, diğer tüm cevapları eski olarak adlandırmam .
Mass Dot Net

10

Bunun miras yoluyla yapılamayacağından kesinlikle emin misiniz? Yansıma, bir sorunu çözerken bakmanız gereken en son şeydir, yeniden düzenleme, kodunuzu anlama ve otomatik analizleri daha zor hale getirir.

DynMethod'unuzu geçersiz kılan bir DrawItem1, DrawItem2, vb. Sınıfınız olması gerektiği anlaşılıyor.


1
@ Fatura K: Diğer koşullar göz önüne alındığında, bunun için miras kullanılmamasına, dolayısıyla yansıma kullanımına karar verdik. Çoğu durumda, bunu bu şekilde yaparız.
Jeromy Irvine

8

Özellikle özel üyelere yansıma yanlış

  • Yansıma, güvenlik türünü bozar. Var olmayan bir yöntemi (artık) ya da yanlış parametrelerle ya da çok fazla parametreyle ya da yeterli değil ... hatta yanlış sırada (bu benim favorim :)) çağırmayı deneyebilirsiniz. Bu arada dönüş tipi de değişebilir.
  • Yansıma yavaş.

Özel üyelerin yansıması, kapsülleme ilkesini ihlal eder ve böylece kodunuzu aşağıdakilere maruz bırakır:

  • Kodunuzun karmaşıklığını artırın çünkü sınıfların iç davranışlarını işlemek zorundadır. Gizli olan gizli kalmalıdır.
  • Kodunuzun derleneceğinden kırılmasını kolaylaştırır, ancak yöntem adını değiştirdiğinde çalışmaz.
  • Özel kodun kırılmasını kolaylaştırır, çünkü özel ise bu şekilde adlandırılması amaçlanmamıştır. Belki de özel yöntem çağrılmadan önce bazı içsel durum bekler.

Yine de yapsam ne olur?

Bu nedenle, üçüncü bir tarafa bağımlı olduğunuzda veya açıkta olmayan bir API'ye ihtiyacınız olduğunda, biraz düşünmeniz gerekir. Bazıları da sahip oldukları bazı sınıfları test etmek için kullanırlar, ancak sadece testler için iç üyelere erişim sağlamak için arabirimi değiştirmek istemezler.

Eğer yaparsan, doğru yap

  • Kırılması kolay hafifletmek:

Kolay kırılma sorununu hafifletmek için en iyisi, sürekli entegrasyon derlemesinde veya benzeri bir şekilde çalışacak birim testlerinde test ederek olası bir kopukluğu tespit etmektir. Tabii ki, her zaman aynı montajı kullandığınız anlamına gelir (özel üyeleri içerir). Dinamik bir yük ve yansıma kullanıyorsanız, ateşle oynamayı seversiniz, ancak her zaman çağrının üretebileceği İstisnayı yakalayabilirsiniz.

  • Yansımanın yavaşlığını azaltın:

Net Framework'ün son sürümlerinde, CreateDelegate MethodInfo tarafından çağrılan bir faktör 50'yi geçmiştir:

// The following should be done once since this does some reflection
var method = this.GetType().GetMethod("Draw_" + itemType, 
  BindingFlags.NonPublic | BindingFlags.Instance);

// Here we create a Func that targets the instance of type which has the 
// Draw_ItemType method
var draw = (Func<TInput, Output[]>)_method.CreateDelegate(
                 typeof(Func<TInput, TOutput[]>), this);

drawçağrılar, standart olarak MethodInfo.Invoke kullanılmasından yaklaşık 50 kat daha hızlı olacaktır :drawFunc

var res = draw(methodParams);

Bu kontrol mayın görevini farklı metot çağrımı üzerinde kriter görmek için


1
Bağımlılık enjeksiyonunun tercih edilen bir birim test yöntemi olmasına rağmen, dikkatli bir şekilde kullanılması, test için erişilemeyecek erişim birimlerine erişim kullanmak için tamamen kötü değildir. Şahsen, normal [genel] [korumalı] [özel] değiştiricilerin yanı sıra [Test] [Kompozisyon] değiştiricilerine de sahip olmamız gerektiğini düşünüyorum, böylece her şeyi herkese açık hale getirmeye zorlanmadan bu aşamalarda bazı şeylerin görünür olması mümkündür ( ve bu nedenle bu yöntemleri tam olarak belgelemek zorunda)
Andrew Pate

1
Özel üyelere yansıtma sorunlarını listelediği için teşekkürler Fab, onu kullanma hakkında nasıl hissettiğimi gözden geçirmeme neden oldu ve bir sonuç geldi ... Özel üyelerinizi birim test etmek için yansımayı kullanmak yanlış, ancak kod yollarını test etmek gerçekten çok yanlış.
andrew pate

2
Ancak genellikle birim olarak test edilemeyen eski kodların birim testi söz konusu olduğunda, bunu yapmak için mükemmel bir yoldur
TS

2

Çizmek istediğiniz her tür için yalnızca farklı bir Çizim yönteminiz olmaz mı? Ardından çizilecek itemType türündeki nesneden geçen aşırı yüklenmiş Draw yöntemini çağırın.

Sorunuz itemType'ın gerçekten farklı türdeki nesnelere atıfta bulunup bulunmadığını netleştirmez.


1

Ben bunu geçebilir düşünüyorum BindingFlags.NonPublicnerede o olduğunu GetMethodyöntemi.


1

Nesne örneğindeki koruma düzeyine rağmen herhangi bir yöntemi çağırır. Zevk almak!

public static object InvokeMethod(object obj, string methodName, params object[] methodParams)
{
    var methodParamTypes = methodParams?.Select(p => p.GetType()).ToArray() ?? new Type[] { };
    var bindingFlags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static;
    MethodInfo method = null;
    var type = obj.GetType();
    while (method == null && type != null)
    {
        method = type.GetMethod(methodName, bindingFlags, Type.DefaultBinder, methodParamTypes, null);
        type = type.BaseType;
    }

    return method?.Invoke(obj, methodParams);
}

0

Bunun nereye gittiğini ve bu konudaki bazı kişilerin neden "hala çalışmıyor" olduğundan şikayet etmek için bu (tamamlayıcı) yanıtı (bazen cevaptır) okuyun.

Buradaki cevaplardan biriyle aynı kodu yazdım . Ama hala bir sorunum vardı. Kırılma noktası koydum

var mi = o.GetType().GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance );

Yapıldı ama mi == null

Ve ben bütün projelerde yeniden inşa edene kadar böyle davranmaya devam etti. Yansıtma yöntemi üçüncü montajda otururken bir montajı birim test ediyordum. Tamamen kafa karıştırıcıydı ama yöntemleri keşfetmek için Anında Pencere kullandım ve birim testi denemeye çalıştığım özel bir yöntemin eski adı olduğunu gördüm (yeniden adlandırdım). Bu bana, birim test projesi kurulsa bile eski montajın veya PDB'nin hala orada olduğunu söyledi - bir nedenden ötürü testler yapılmadı. "yeniden inşa" çalıştı


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.