Advantages of the generalized code, over not generalized:
Type safety. When a generalized algorithm is applied with a specific type, the compiler and the CLR understand this and ensure that the algorithm uses only objects compatible with this data type. Attempting to use an incompatible object will result in a compile-time error or an exception at run time.
SomeType<Stream> some = new SomeType<Stream>() declaring SomeType<Stream> some = new SomeType<Stream>() , you can store only objects of the Stream type or derivatives of it in your storage variable. Verification will occur at the stage of writing the code, i.e. will immediately issue a warning, unlike object - at the execution stage.
- More simple and clear code. Since the compiler provides type safety, the source text requires less type conversion operation, and such code is easier to write and maintain.
- Productivity increase. Before the appearance of generalizations, one of the ways to define a generalized algorithm was to define all its members in such a way that they could βworkβ with the Object data type. For the algorithm to work with instances of a significant type, the CLR had to package this instance before calling members of the algorithm. Packaging requires memory allocation in a manageable heap, which leads to more frequent garbage collection procedures, and this, in turn, reduces application performance. Since a generalized algorithm can be created to work with a particular significant type, instances of a significant type can be passed by value and the CLR does not need to be packaged. Type casting operations are also not needed, so the CLR does not need to control type safety when converting them, which also speeds up code performance.
To evaluate the performance, we used a non-generalized ArrayList from the FCL class library and a generalized List .
Main method - call two tests
public static void Main() { ValueTypePerfTest(); ReferenceTypePerfTest(); }
Testing significant types:
private static void ValueTypePerfTest() { const Int32 count = 10000000; using (new OperationTimer("List<Int32>")) { List<Int32> l = new List<Int32>(); for (Int32 n = 0; n < count; n++) { l.Add(n); // ΠΠ΅Π· ΡΠΏΠ°ΠΊΠΎΠ²ΠΊΠΈ Int32 x = l[n]; // ΠΠ΅Π· ΡΠ°ΡΠΏΠ°ΠΊΠΎΠ²ΠΊΠΈ } l = null; // ΠΠ»Ρ ΡΠ΄Π°Π»Π΅Π½ΠΈΡ Π² ΠΏΡΠΎΡΠ΅ΡΡΠ΅ ΡΠ±ΠΎΡΠΊΠΈ ΠΌΡΡΠΎΡΠ° } using (new OperationTimer("ArrayList of Int32")) { ArrayList a = new ArrayList(); for (Int32 n = 0; n < count; n++) { a.Add(n); // Π£ΠΏΠ°ΠΊΠΎΠ²ΠΊΠ° Int32 x = (Int32) a[n]; // Π Π°ΡΠΏΠ°ΠΊΠΎΠ²ΠΊΠ° } a = null; // ΠΠ»Ρ ΡΠ΄Π°Π»Π΅Π½ΠΈΡ Π² ΠΏΡΠΎΡΠ΅ΡΡΠ΅ ΡΠ±ΠΎΡΠΊΠΈ ΠΌΡΡΠΎΡΠ° } }
Reference Type Testing:
private static void ReferenceTypePerfTest() { const Int32 count = 10000000; using (new OperationTimer("List<String>")) { List<String> l = new List<String>(); for (Int32 n = 0; n < count; n++) { l.Add("X"); // ΠΠΎΠΏΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ ΡΡΡΠ»ΠΊΠΈ String x = l[n]; // ΠΠΎΠΏΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ ΡΡΡΠ»ΠΊΠΈ } l = null; // ΠΠ»Ρ ΡΠ΄Π°Π»Π΅Π½ΠΈΡ Π² ΠΏΡΠΎΡΠ΅ΡΡΠ΅ ΡΠ±ΠΎΡΠΊΠΈ ΠΌΡΡΠΎΡΠ° } using (new OperationTimer("ArrayList of String")) { ArrayList a = new ArrayList(); for (Int32 n = 0; n < count; n++) { a.Add("X"); // ΠΠΎΠΏΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ ΡΡΡΠ»ΠΊΠΈ String x = (String) a[n]; // ΠΡΠΎΠ²Π΅ΡΠΊΠ° ΠΏΡΠ΅ΠΎΠ±ΡΠ°Π·ΠΎΠ²Π°Π½ΠΈΡ } // ΠΈ ΠΊΠΎΠΏΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ ΡΡΡΠ»ΠΊΠΈ a = null; // ΠΠ»Ρ ΡΠ΄Π°Π»Π΅Π½ΠΈΡ Π² ΠΏΡΠΎΡΠ΅ΡΡΠ΅ ΡΠ±ΠΎΡΠΊΠΈ ΠΌΡΡΠΎΡΠ° } }
Class for estimating the time of operations
internal sealed class OperationTimer : IDisposable { private Int64 m_startTime; private String m_text; private Int32 m_collectionCount; public OperationTimer(String text) { PrepareForOperation(); m_text = text; m_collectionCount = GC.CollectionCount(0); // ΠΡΠ° ΠΊΠΎΠΌΠ°Π½Π΄Π° Π΄ΠΎΠ»ΠΆΠ½Π° Π±ΡΡΡ ΠΏΠΎΡΠ»Π΅Π΄Π½Π΅ΠΉ Π² ΡΡΠΎΠΌ ΠΌΠ΅ΡΠΎΠ΄Π΅ // Π΄Π»Ρ ΠΌΠ°ΠΊΡΠΈΠΌΠ°Π»ΡΠ½ΠΎ ΡΠΎΡΠ½ΠΎΠΉ ΠΎΡΠ΅Π½ΠΊΠΈ Π±ΡΡΡΡΠΎΠ΄Π΅ΠΉΡΡΠ²ΠΈΡ m_startTime = Stopwatch.StartNew(); } public void Dispose() { Console.WriteLine("{0} (GCs={1,3}) {2}", (m_stopwatch.Elapsed), GC.CollectionCount(0), m_collectionCount, m_text); } private static void PrepareForOperation() { GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); } }
Test results
00:00:01.6246959 (GCs= 6) List<Int32> 00:00:10.8555008 (GCs=390) ArrayList of Int32 00:00:02.5427847 (GCs= 4) List<String> 00:00:02.7944831 (GCs= 7) ArrayList of String
Conclusion
With the Int32 type, the generic List algorithm runs much faster than the non-generic ArrayList algorithm. Moreover, the difference is huge: 1.6 seconds versus 11 seconds, that is , 7 times faster ! In addition, using the meaningful type (Int32) with the ArrayList algorithm requires a lot of packing operations, and, as a result, 390 garbage collection procedures , and there are only 6 in the List algorithm.
The test results for the reference type are not so impressive: the time indicators and the number of garbage collection operations here are about the same . Therefore, in this case, the generalized List algorithm has no real advantages. However, remember that using a generalized algorithm greatly simplifies code and type control during compilation .
Source: Jeffrey Richter "CLR via C #"
objecttype? And say, what is convenient and where to apply? Since this is a question, I think it is worth explaining the reason for this question. Itβs just not quite clear to me why to implement a deliberately erroneous approach that will lead to problems, and you can guess it yourself, as can be seen from the question. - Denis Bubnov