Yalnızca çalışma zamanında bilinen bir tür parametresiyle genel bir yöntemin çağrılması dynamic
, yansıma API'si yerine bir tür kullanılarak büyük ölçüde basitleştirilebilir .
Bu tekniği kullanmak için türün gerçek nesneden bilinmesi gerekir (yalnızca Type
sınıfın bir örneği değil ). Aksi takdirde, bu tür bir nesne oluşturmanız veya standart yansıma API çözümünü kullanmanız gerekir . Activator.CreateInstance yöntemini kullanarak bir nesne oluşturabilirsiniz .
Eğer genel bir yöntem çağırmak istiyorsanız, "normal" kullanımda türünü çıkarmış olurdu, o zaman sadece bilinmeyen tip nesnesi döküm geliyor dynamic
. İşte bir örnek:
class Alpha { }
class Beta { }
class Service
{
public void Process<T>(T item)
{
Console.WriteLine("item.GetType(): " + item.GetType()
+ "\ttypeof(T): " + typeof(T));
}
}
class Program
{
static void Main(string[] args)
{
var a = new Alpha();
var b = new Beta();
var service = new Service();
service.Process(a); // Same as "service.Process<Alpha>(a)"
service.Process(b); // Same as "service.Process<Beta>(b)"
var objects = new object[] { a, b };
foreach (var o in objects)
{
service.Process(o); // Same as "service.Process<object>(o)"
}
foreach (var o in objects)
{
dynamic dynObj = o;
service.Process(dynObj); // Or write "service.Process((dynamic)o)"
}
}
}
Ve işte bu programın çıktısı:
item.GetType(): Alpha typeof(T): Alpha
item.GetType(): Beta typeof(T): Beta
item.GetType(): Alpha typeof(T): System.Object
item.GetType(): Beta typeof(T): System.Object
item.GetType(): Alpha typeof(T): Alpha
item.GetType(): Beta typeof(T): Beta
Process
iletilen bağımsız değişkenin gerçek türünü ( GetType()
yöntemi kullanarak ) ve genel parametrenin türünü ( typeof
işleç kullanarak ) yazan genel bir örnek yöntemidir .
Nesne bağımsız değişkenini dynamic
tür yazarak, çalışma zamanına kadar type parametresini sağlamayı erteledik. Ne zaman Process
metot ile çağrılır dynamic
argüman sonra derleyici bu argüman türü hakkında umursamıyor. Derleyici, çalışma zamanında gerçekte aktarılan argüman türlerini (yansıma kullanarak) kontrol eden ve çağrılacak en iyi yöntemi seçen kod üretir. Burada sadece bir genel yöntem var, bu yüzden uygun bir tür parametresiyle çağrıldı.
Bu örnekte, çıktı sizin yazdığınız gibidir:
foreach (var o in objects)
{
MethodInfo method = typeof(Service).GetMethod("Process");
MethodInfo generic = method.MakeGenericMethod(o.GetType());
generic.Invoke(service, new object[] { o });
}
Dinamik tipte versiyon kesinlikle daha kısa ve yazımı daha kolaydır. Ayrıca, bu işlevi birden çok kez çağırmanın performansı hakkında endişelenmemelisiniz. DLR'deki önbellek mekanizması sayesinde aynı türden argümanlarla bir sonraki çağrı daha hızlı olmalıdır . Tabii ki, çağrılan delegeleri önbelleğe alan bir kod yazabilirsiniz, ancak dynamic
türü kullanarak bu davranışı ücretsiz olarak alabilirsiniz.
Aramak istediğiniz genel yöntemin parametreli bir türden bağımsız değişkeni yoksa (bu nedenle tür parametresi çıkartılamaz), aşağıdaki yöntemdeki gibi bir yardımcı yöntemde genel yöntemin çağrılmasını sarabilirsiniz:
class Program
{
static void Main(string[] args)
{
object obj = new Alpha();
Helper((dynamic)obj);
}
public static void Helper<T>(T obj)
{
GenericMethod<T>();
}
public static void GenericMethod<T>()
{
Console.WriteLine("GenericMethod<" + typeof(T) + ">");
}
}
Artan tip güvenliği
dynamic
Nesneyi yansıma API'sini kullanmak yerine kullanmakla ilgili gerçekten harika olan şey, yalnızca çalışma zamanına kadar bilmediğiniz bu türün derleme zamanı denetimini kaybetmenizdir. Diğer argümanlar ve yöntemin adı derleyici tarafından her zamanki gibi statik olarak analiz edilir. Daha fazla bağımsız değişken kaldırır veya eklerseniz, türlerini değiştirin veya yöntem adını yeniden adlandırın, ardından derleme zamanı hatası alırsınız. Yöntem adını bir dize olarak Type.GetMethod
ve bağımsız değişkenleri nesneler dizisi olarak sağlarsanız bu gerçekleşmez MethodInfo.Invoke
.
Aşağıda, derleme zamanında (yorumlanmış kod) ve çalışma zamanında bazı hataların nasıl yakalanabileceğini gösteren basit bir örnek verilmiştir. Ayrıca, DLR'nin hangi yöntemi çağıracağını nasıl çözmeye çalıştığını da gösterir.
interface IItem { }
class FooItem : IItem { }
class BarItem : IItem { }
class Alpha { }
class Program
{
static void Main(string[] args)
{
var objects = new object[] { new FooItem(), new BarItem(), new Alpha() };
for (int i = 0; i < objects.Length; i++)
{
ProcessItem((dynamic)objects[i], "test" + i, i);
//ProcesItm((dynamic)objects[i], "test" + i, i);
//compiler error: The name 'ProcesItm' does not
//exist in the current context
//ProcessItem((dynamic)objects[i], "test" + i);
//error: No overload for method 'ProcessItem' takes 2 arguments
}
}
static string ProcessItem<T>(T item, string text, int number)
where T : IItem
{
Console.WriteLine("Generic ProcessItem<{0}>, text {1}, number:{2}",
typeof(T), text, number);
return "OK";
}
static void ProcessItem(BarItem item, string text, int number)
{
Console.WriteLine("ProcessItem with Bar, " + text + ", " + number);
}
}
Burada yine argümanı dynamic
türe çevirerek bir yöntem uygularız . Yalnızca ilk bağımsız değişkenin türünün doğrulanması çalışma zamanına ertelenir. Aradığınız yöntemin adı yoksa veya başka bağımsız değişkenler geçersizse (yanlış sayıda bağımsız değişken veya yanlış tür) bir derleyici hatası alırsınız.
Sınavı geçtikten sonra dynamic
bir yönteme argüman sonra bu çağrı olduğunu son zamanlarda bağlanmış . Yöntem aşırı yük çözünürlüğü çalışma zamanında gerçekleşir ve en iyi aşırı yükü seçmeye çalışır. Bu nedenle, ProcessItem
yöntemi bir tür nesneyle BarItem
çağırırsanız, genel olmayan yöntemi çağıracaksınız, çünkü bu tür için daha iyi bir eşleşmedir. Ancak, Alpha
bu nesneyi işleyebilecek bir yöntem olmadığından türün bir argümanını ilettiğinizde bir çalışma zamanı hatası alırsınız (genel bir yöntem kısıtlamaya sahiptir where T : IItem
ve Alpha
sınıf bu arabirimi uygulamaz). Ama bütün mesele bu. Derleyicide bu çağrının geçerli olduğu bilgisi yok. Bir programcı olarak bunu biliyorsunuz ve bu kodun hatasız çalıştığından emin olmalısınız.
Dönüş türü gotcha
Dinamik türünün bir parametresi olmayan bir boşluk yöntemini çağırarak bittiğinde, döndürme türü muhtemelen olacaktır olmak dynamic
çok . Dolayısıyla, önceki örneği bu kodla değiştirirseniz:
var result = ProcessItem((dynamic)testObjects[i], "test" + i, i);
sonuç nesnesinin türü olur dynamic
. Bunun nedeni derleyicinin hangi yöntemin çağrıldığını her zaman bilmemesidir. İşlev çağrısının dönüş türünü biliyorsanız , kodun geri kalanı statik olarak yazılması için bunu örtük olarak gerekli türe dönüştürmelisiniz :
string result = ProcessItem((dynamic)testObjects[i], "test" + i, i);
Tür eşleşmezse bir çalışma zamanı hatası alırsınız.
Aslında, önceki örnekte sonuç değerini almaya çalışırsanız, ikinci döngü yinelemesinde bir çalışma zamanı hatası alırsınız. Bunun nedeni, bir void işlevinin dönüş değerini kaydetmeye çalışmanızdır.