I am interested in this issue in terms of performance. About the fact that object requires type conversion and because of this, you can implicitly make a mistake, I know.

UPD. This question was asked at the interview. As an example, added 2 classes:

class SomeClass<T> { private T[] storage; } class SomeClass { private object[] storage; } 
  • Could you give an example of a generic class implementation through the object type? 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
  • Added an example. This question I was asked at the interview. - Andrei Khotko
  • I already thought that you consciously decided to implement this))) for an interview a good question) - Denis Bubnov

2 answers 2

In the event that you use meaningful types, you receive additional overhead costs for packaging and subsequent unpacking.

 var numbers = new List<object>(); numbers.Add(1); // ΡƒΠΏΠ°ΠΊΠΎΠ²ΠΊΠ° β„–1 numbers.Add(2); // ΡƒΠΏΠ°ΠΊΠΎΠ²ΠΊΠ° β„–2 int n1 = (int)numbers[0]; // распаковка β„–1 int n2 = (int)numbers[1]; // распаковка β„–2 

Well, as you yourself mentioned, such use is not type safe.

Generic collections were introduced in .NET 2.0 just to avoid these problems. In earlier versions, ArrayList used, which, by and large, is an analogue of List<object> .

UPD

In the example you cited, everything is the same: in the event that the instance of significant types is added to the storage array, packaging will occur.

  • I did not quite understand about the packaging and unpacking. And if we are not talking about collections, but about an array of integers int[] items , will there be a difference between casting object[] items to int[] items and through the parameterization of T[] items ? If you can, tell us more about the type specification process in either case. - Andrei Khotko
  • The difference is that in the case of object[] you need to cast to int[] , and in the case of T[] , the cast is not needed at all. Because when using SomeClass<int> compiler will generate a class in which the field will be a private int[] storage . And so for each parameter type. - andreycha
  • Those. This question for SomeClass<int> is solved at the compilation stage? - Andrei Khotko
  • 2
    Yes. Be sure to read something about how generalizations work. Apparently, you are only getting acquainted with C # and .NET, so I highly recommend reading Richter’s book "CLR via C #". A more complete list of references is here . - andreycha
  • Thank you for your help! - Andrei Khotko

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 #"

  • On the first point is not clear. Can I use ArrayList 'without sources? - VladD
  • @VladD You do not understand me correctly. This is a comparison with C ++ and it mentions access to the class code, and not to the build - Vadim Prokopchuk
  • I understood, yes, but the question did not seem to mention this? The question is about the difference between T[] storage and object[] storage in C # , isn't it? - VladD
  • @VladD corrected. The current answer does not go beyond C # - Vadim Prokopchuk
  • Yeah thanks! (My +1 is already standing.) - VladD