W swoich potyczkach dosyć często mam do czynienia z COM-ami, przy tworzeniu instancji których równie często sięgam po Activator.CreateInstance. I pewnie nie byłoby w tym nic odkrywczego ani niezwykłego, gdyby nie pewna obserwacja.
Weźmy dla przykładu kawałek kodu, w którym tworzymy instancję klasy TestClass korzystając z wersji genericsowej i zwykłej:
class TestClass
{
public TestClass()
{
}
}
class Program
{
const Int32 CYCLES = 100000;
static void Main(string[] args)
{
CreateInstanceGenericTest();
CreateInstanceNonGenericTest();
}
private static void CreateInstanceNonGenericTest()
{
using (OperationTimer ot = new OperationTimer("CreateInstance NON Generic"))
{
TestClass t = null;
for (int i = 0; i < CYCLES; i++)
{
t = (TestClass)Activator.CreateInstance(typeof(TestClass));
}
}
}
private static void CreateInstanceGenericTest()
{
using (OperationTimer ot = new OperationTimer("CreateInstance Generic"))
{
TestClass t = null;
for (int i = 0; i < CYCLES; i++)
{
t = (TestClass)Activator.CreateInstance<TestClass>(); //jedyna różnica
}
}
}
}
po wykonaniu którego dostajemy czasy:
,375 seconds CreateInstance Generic
,574 seconds CreateInstance NON Generic
Czyli wszystko mniej-więcej zgodnie z oczekiwaniami. Wersja genericsowa nieznacznie sprawniejsza (powiedzmy dwukrotnie).
Dokonajmy jednak prostej modyfikacji. Zmieńmy mianowicie modyfikator dostępu dla klasy TestClass z internal na public, pozostawiając resztę bez zmian:
public class TestClass
{
public TestClass()
{
}
}
a otrzymamy następujące czasy:
,372 seconds CreateInstance Generic
,035 seconds CreateInstance NON Generic
Jak widać powyższa zmiana zupełnie nie wpłynęła na wersję genericsową, natomiast spowodowała prawie 20x przyspieszenie wersji niegenericsowej, wobec czego wersja nie używająca generics jest teraz o rząd wielkości szybsza od odpowiednika genericsowego. Wow!
Ale o so chodzi?! :) Mam nadzieję, że ktoś wie jaka idea przyświecała takiemu rozróżnieniu i wytłumaczy mi to łopatologicznie :)
PS. Pomocnicza klasa OperationTimer mierząca czas wykonania kodu wewnątrz bloku using jest żywcem ściągnięta z książki 'CLR via C#' Jeffreya Richtera i opiera się na klasie Stopwatch z System.Diagnostics.
PS2. W przypadku zagnieżdżenia klasy TestClass niezależnie od wybranego modyfikatora dostępu wyniki są takie jak w przypadku pierwszym, czyli wersja genericsowa szybsza.