#671 – A Base Class Constructor Can Call a Virtual Method

If a base class constructor calls a virtual method that is overridden in a derived class, the version of the method in the derived class is the one that will get called.

For example, assume that we have a Dog class that defines a virtual Bark method and a Terrier subclass that overrides the Bark method.

    public class Dog
    {
        public Dog()
        {
            Console.WriteLine("Dog constructor");
            Bark();
        }

        public virtual void Bark()
        {
            Console.WriteLine("Woof");
        }
    }

    public class Terrier : Dog
    {
        public Terrier()
            : base()
        {
            Console.WriteLine("Terrier constructor");
        }

        public override void Bark()
        {
            Console.WriteLine("Terrier barking!");
        }
    }

Assume you create an instance of a Terrier.

            Terrier t = new Terrier();

The Terrier constructor calls the Dog constructor, which invokes the Bark method.  But it’s the Bark method in Terrier that is called, rather than the Bark method in Dog.

#670 – Static vs. Instance Initialization

Static fields and properties are data members associated with a class itself, rather with instances of that class.  A static constructor can be used to initialize static members.  Static fields can also be initialized when they are declared.

The first time that you reference a static member, the following occurs:

  • All static fields are initialized
  • The static constructor is called
  • Your code, which accesses the static member, executes

If you reference an instance method, or create an instance of the class, before referencing any static members, the following occurs:

  • All static fields are initialized
  • The static constructor is called
  • All instance fields are initialized
  • The instance constructor is called
  • Your code, which accesses an instance member, executes

For example, initializing an instance of a Dog leads to the output shown below.

Dog d = new Dog();

#669 – Initializing Fields by Calling A Method

You can initialize fields in a class at the point where you declare them, using a constant.  You can also initialize a field to a value returned from a call to a method–as long as the method is not an instance method within the same class.

Here’s an example, where we call a static method in the same class to initialize a field.

    public class Dog
    {
        private static string GenerateCreationInfo()
        {
            return string.Format("I was created at {0}", DateTime.Now);
        }

        public string CreationInfo = GenerateCreationInfo();

        public string Name { get; set; }
        public int Age { get; set; }

        public Dog(string name, int age)
        {
            Name = name;
            Age = age;
        }
    }

#668 – GetTotalMemory Indicates How Much Memory You’ve Allocated

You can call the GC.GetTotalMemory function to find out the total # bytes you’ve allocated so far on the managed heap.  Note that this number does not include other memory that your process may have allocated, including things like memory allocated in unmanaged code.

        static void Main()
        {
            try
            {
#if DEBUG
                GC.Collect();
#endif
                Console.WriteLine(string.Format("Before we start, total mem alloc'd {0} bytes", GC.GetTotalMemory(false)));

                while (true)
                {
                    ConsoleKeyInfo cki = Console.ReadKey();

                    int size = 1024 * 1024 * 100;  // 100MB
                    byte[] data = new byte[size];

                    Array.Clear(data, 0, size);

                    _bigList.Add(data);

#if DEBUG
                    GC.Collect();
#endif
                    Console.WriteLine(string.Format("Total mem alloc'd now {0} bytes", GC.GetTotalMemory(false)));
                }
            }
            catch (Exception xx)
            {
                Console.WriteLine(xx.ToString());
                Console.ReadLine();
                }
        }

#667 – .NET Memory Performance Counters Updated When Garbage Collector Runs

The .NET CLR Memory performance counters can be used to track memory usage for CLR-based (.NET) applications.  The counters, however, are only updated when the garbage collector runs.  This means that if you’re watching one of the counters, you won’t necessarily see it update immediately after your application allocates some memory.

If you are doing some debugging or profiling related to memory management, you can force garbage collection at the point where you want to measure memory usage.  You do this using the GC.Collect method.  GC.Collect should not typically be called explicitly in production code.

    public class Program
    {
        private static List<object> _bigList = new List<object>();

        static void Main()
        {
            try
            {
#if DEBUG
                GC.Collect();
#endif

                while (true)
                {
                    ConsoleKeyInfo cki = Console.ReadKey();

                    int size = 1024 * 1024;  // 1MB
                    byte[] data = new byte[size];

                    Array.Clear(data, 0, size);

                    _bigList.Add(data);

#if DEBUG
                    GC.Collect();
#endif
                }
            }
            catch (Exception xx)
            {
                Console.WriteLine(xx.ToString());
                Console.ReadLine();
                }
        }
    }

#666 – Looking at .NET Memory Usage Using Performance Counters

As you create new objects in .NET, the garbage collector allocates memory for the objects on the heap.  As memory is allocated, the process consumes some of its available virtual memory.  (In practice, a 32-bit process can allocate at most around 1.5GB of its 2GB maximum).

You can keep track of (roughly) how much memory on the managed heap that your application has allocated by using performance counters.

To track memory usage:

  • Start Performance Monitor

 

  • Start your application
  • Click the “+” icon to add a counter

  • Expand the .NET CLR Memory section

  • Select the # Total Reserved Bytes counter and select your application

  • Click the Add button and then click OK

You’ll now see a graph indicating total number of bytes of memory that your application has reserved.  The Last field will show you the most recent value.

#665 – Maximum Amount of Virtual Memory

In 32-bit versions of Windows, a process has access to at most 4GB of virtual memory.  Of this, an application can access at most 2GB.  The remaining 2GB is reserved for the operating system.

The 4GB limit comes from the fact that 32-bit words are used as addresses into memory, resulting in a maximum of 4,294,967,296 bytes–or 4GB.

In 64-bit versions of Windows (x64), we get a 64-bit address space, resulting in 18,446,744,073,709,551,616 possible memory locations, or 16 exabytes (equivalent to 16,777,216 terabytes, or 17,179,869,184 GB).  An application running on 64-bit Windows, however, only gets access to a tiny fraction of the available total.  Each application can address a maximum of 8TB (or 8,192GB, which is still 4,096 times what a 32-bit app has access to).

#664 – Physical Memory vs. Virtual Memory

When writing software, you’ll often come across the terms physical memory and virtual memory.  Here’s a quick explanation.

Physical memory refers to the actual random-access memory (RAM) installed in your computer.  On newer desktops and laptops, this is typically from 4-16GB.  RAM is a place to store data.  It allows the operating system and programs running on it to read and write data while the computer is running.

Virtual memory is an area of memory that an application can read from and write to which behaves like a chunk of physical memory.  The application can freely read from and write to this memory without worrying about affecting other applications.  This chunk of virtual memory may be smaller or larger than the actual physical memory on the machine.

#663 – Visual Studio Debugger Will Call Your Object’s ToString Method

When you examine the value of a variable in the Visual Studio debugger, the debugger will call the object’s ToString method in order to display a single value for the object.  If the object’s type contains other members, you can typically expand the display to show the various members.

What this means for custom types is that you’ll see the results of the default implementation of ToString, which is to just display the name of the type.  We can see this by looking at a couple of Dog objects in the Locals window of the debugger.  Note that the “value” of each is just listed as DogLibrary.Dog.

However, if we implement the Dog.ToString method and have it dump out the dog’s name and age, we’ll now see that information in the debugger.

This demonstrates why it’s often useful, for debugging purposes, to implement ToString.

#662 – Overriding the ToString Method for a Custom Type

Every type inherits a ToString method, since every type inherits, directly or indirectly, from System.Object.  For a custom type, this method will by default just display the name of the type.

Dog kirby = new Dog("Kirby", 13);

Console.WriteLine(kirby.ToString());

You can, however, override the ToString method in your class so that it provides information about the specific instance of the class.  In the example below, we override Dog.ToString.

    public class Dog
    {
        public string Name { get; set; }
        public int Age { get; set; }

        public Dog(string name, int age)
        {
            Name = name;
            Age = age;
        }

        public override string ToString()
        {
            return string.Format("Dog [{0}] is {1} years old", Name, Age);
        }
    }