Monday, 24 February 2014

Static Vs Non Static Method in C#


Static Vs Non Static Method in C#


Today I was browsing through the asp.net forums. I came across a post by AidyF, where He have explained the difference between Static and Non static Method in C# in a very deliberate manner. I would like to share the same with you guys..

First lets start with MSDN definition:

MSDN Definition: A static class is basically the same as a non-static class, but there is one difference: a static class cannot be instantiated. In other words, we cannot use the new keyword to create a variable of the class type. Because there is no instance variable, we access the members of a static class by using the class name itself. 
C# fields must be declared inside a class. However, if we declare a method or a field as static, we can call the method or access the field using the name of the class. No instance is required. We can also use the static keyword when defining a field. With this feature, we can create a single field that is shared among all objects created from a single class. Non-static fields are local to each instance of an object. 
When you define a static method or field, it does not have access to any instance fields defined for the class; it can use only fields that are marked as static. Furthermore, it can directly invoke only other methods in the class that are marked as static; nonstatic (instance) methods or fields first require creation of an object on which to call them.  

Now let us look into a basic example:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace WebTest
{
    public class Maths
    {
        public int NumberA { get; set; }
        public int NumberB { get; set; }
        public int Result { get; set; }

        public int Add()
        {
            // Return the sum from the object's numbers
            return NumberA + NumberB;
        }

        public int AddWithParams(int numberA, int numberB)
        {
            // We can still use parameters rather than the object's properties if we need to
            return numberA + numberB;
        }

        public void Subtract()
        {
            // Rather than return the subtraction, we'll store it
            // These methods aren't supposed to make total sense, they
            // are just demonstrating the theory

            Result = NumberA - NumberB;
        }

    }

    public class MathsStatic
    {
        public static int Result { get; set; }

        public static int Add(int numberA, int numberB)
        {
            return numberA + numberB;
        }

        public static void Subtract(int numberA, int numberB)
        {
            // If you write to static variables without locking
            // You're gonna have a bad time.
            // This code is for demonstration purposes only
            
            Result = numberA - numberB;
        }
    }

    public partial class StaticMaths : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            Maths mathsOne = new Maths();

            mathsOne.NumberA = 1;
            mathsOne.NumberB = 3;

            // This will write 4
            System.Diagnostics.Debug.WriteLine(mathsOne.Add());

            Maths mathsTwo = new Maths();

            mathsTwo.NumberA = 2;
            mathsTwo.NumberB = 1;

            // This will write 3
            System.Diagnostics.Debug.WriteLine(mathsTwo.Add());

            // This is still 4 as its state hasn't changed, mathsOne and mathsTwo are different objects
            System.Diagnostics.Debug.WriteLine(mathsOne.Add());

            mathsOne.NumberA = mathsOne.NumberA + 1;

            // Now it's 5
            System.Diagnostics.Debug.WriteLine(mathsOne.Add());
            
            // This is still 3
            System.Diagnostics.Debug.WriteLine(mathsTwo.Add());

            // mathsOne and mathsTwo are copies of Maths, each with their own internal state.
            // They carry this state everywhere with them, even if I store the object then
            // retrieve it

            Session["MathsObject"] = mathsOne;

            Maths mathsFromSession = (Maths)Session["MathsObject"];

            // This is 5
            System.Diagnostics.Debug.WriteLine(mathsFromSession.Add());

            // Let's try subtract
            mathsOne.NumberA = 5;
            mathsOne.NumberB = 2;
            mathsOne.Subtract(); // this subtracts the numbers and stores it in "Result"

            // This is 3
            System.Diagnostics.Debug.WriteLine(mathsOne.Result);

            Session["MathsObject"] = mathsOne;

            mathsFromSession = (Maths)Session["MathsObject"];

            // Still 3 as Result is part of the object's state which has survived being
            // stored in the Session and retrieved
            System.Diagnostics.Debug.WriteLine(mathsFromSession.Result);

            // We haven't called the Subtract method on mathsTwo so its Result
            // is still the default value of 0
            System.Diagnostics.Debug.WriteLine(mathsTwo.Result);

            // We'll try AddWithParams, but first we'll note mathsOne's state
            System.Diagnostics.Debug.WriteLine(mathsOne.NumberA); // 5
            System.Diagnostics.Debug.WriteLine(mathsOne.NumberB); // 2
            System.Diagnostics.Debug.WriteLine(mathsOne.Result); // 3

            int result = mathsOne.AddWithParams(10, 15);

            System.Diagnostics.Debug.WriteLine(result); // 25

            // AddWithParams is actually self-contained.  All of its data it gets
            // from parameters, it does all of its own work inside the method
            // and the result is not stored in the object but output direct
            // AddWithParams doesn't need, or change the state of the object at all
            // It is as it was before

            System.Diagnostics.Debug.WriteLine(mathsOne.NumberA); // 5
            System.Diagnostics.Debug.WriteLine(mathsOne.NumberB); // 2
            System.Diagnostics.Debug.WriteLine(mathsOne.Result); // 3

            // If a method doesn't need the object's state then you could make
            // it a static method.  Note that you can add static methods and
            // non-static methods to the same class.

            // Let's see how MathsStatic works

            result = MathsStatic.Add(1, 2);

            System.Diagnostics.Debug.WriteLine(result); // 3

            // It did its work without needing any object state and without using any object state
            // As explained above, this does the same thing as AddWithParams on the Maths class
            // only as a proper static method

            // Let's look at Subtract

            MathsStatic.Subtract(10, 2);

            result = MathsStatic.Result;

            // This is 8, so what's the problem?
            System.Diagnostics.Debug.WriteLine(result);

            // With the non-static Maths class, we had two distinct instances of the class (mathsOne and mathsTwo)
            // with their own private state that wasn't affected by operations on other instances of Maths
            // You'll note we didn't actually make a instance of MathsStatic, the Subtract method and Result
            // property are on the type itself, and the type is global to your app.  There is only one "string" type,
            // one "int" type, one "MathsStatic" type and any of your code that uses these types is using the same
            // global type.  So any static property or method on that type is also global to your app, there is only
            // one copy of them.  So if a different user is on a different page and looks at MathsStatic.Result,
            // they'll see "8" also, but worse still if they do a Subtract then their result will now be read
            // when you look at MathsStatic.Result. Static properties function like global variables

            // To sum up - static methods are for methods that don't need state, and don't alter it
            // They get their data from parameters, and return the results without storing anything in the object
            // Non-static is for when you want an object to keep its state, and for methods to work
            // on the state that is held in that actual instance.
            // These classes tend to be classes that represent "things" in your system.  Things
            // like a Person class with a PersonID, FirstName, LastName, Title etc.  You could give your Person
            // class a GetFullName method that concatinated Title, FirstName and LastName of that Person class
            // That method would be non-static and would act on the propeties currently held in that instance
            // of Person

            // Static classes and methods tend to be for utility or helper functions that do contained
            // units of work.
            
            // As said already you can have both on the same class;

            string myString = "Hello";
            System.Diagnostics.Debug.WriteLine(myString.ToUpper()); // HELLO
            System.Diagnostics.Debug.WriteLine(myString); // Hello

            // ToUpper is non-static, it reads the state of the instance of the class (myString)
            // and does some work on that and returns it from the function, so you get HELLO
            // as a return string.  Note that myString is *still* Hello, because while it used
            // the current state of its instance, ToUpper chooses to not update the state (why that is
            // the case is a subject for another day :) )

            System.Diagnostics.Debug.WriteLine(string.Concat("Hello", " ", "World")); // Hello World

            // Concat is a static method, you'll note we didn't call it on an instance of string like
            // we did with ToUpper.  Because it is static we need to pass it all the data it needs
            // to do the work and get the result right away.  It needs no state and updates no state
            // so it is static.  Remember that Concat and ToUpper are both methods on the same class (string),
            // just one static and the other not

            // One final curveball that you *might* be wondering about, and I hope this final point
            // doesn't confuse you.  What about non-static variables declared *inside* static methods, are
            // they ok?  Yes they are.  If your static method is called by two different processes, each
            // static method gets local copies of all non-static variables that it creates, so your
            // two processes won't be using each other's variables.  This is *not* the case for *static*
            // variables inside static methods.
        }
    }
}


Mixing static and non-static resources and each accessing the other:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace WebTest
{
    public partial class StaticMathsB : System.Web.UI.Page
    {
        // Let's get advanced!
        public class Maths
        {
            public int NumberA { get; set; }
            public int NumberB { get; set; }
            public static int Result { get; private set; }

            public void Add()
            {
                // Note from the declarations that Add is *not* static and Result is *static*

                Result = NumberA + NumberB;
            }
        }

        public class MathsStatic
        {
            public int Result { get; private set; }

            public static void Add(int numberA, int numberB)
            {
                // Note from the declarations that Add is *static* and Result is *not* static

                // Y U NO COMPILE??
                //Result = numberA + numberB;
            }
        }

        protected void Page_Load(object sender, EventArgs e)
        {
            Maths mathsOne = new Maths();
            // These are non-static as before
            mathsOne.NumberA = 1;
            mathsOne.NumberB = 2;
            // This is a non-static method that adds NumberA and NumberB and
            // stores the result in static property Result
            mathsOne.Add();

            //System.Diagnostics.Debug.WriteLine(mathsOne.Result); // Y U NO COMPILE??
            System.Diagnostics.Debug.WriteLine(Maths.Result); // 3;

            // mathsOne.Result doesn't compile...why?
            // Result is static, it exists on the *type*, not an instance of it
            // Maths is the type so Maths.Result exists
            // mathsOne is an *instance* of Maths, so mathsOne.Result does not exist.  Static variables
            // exist on the type (Maths), not the instance (mathsOne)

            Maths mathsTwo = new Maths();
            mathsTwo.NumberA = 5;
            mathsTwo.NumberB = 1;
            mathsTwo.Add();

            System.Diagnostics.Debug.WriteLine(Maths.Result); // 6

            // The problem here is obvious.  Both mathsOne and mathsTwo have their own
            // NumberA and NumberB in their object state, but as Result is static and
            // exists on the type, it is shared.  Not just for this code on this page
            // for this user, but on any code on any page for any user.  Storing the
            // result in this way is obviously unsuable and you wouldn't do it.

            MathsStatic.Add(8, 2);
            // Look at the code in the Add method above, it doesn't compile.  Why?

            //public static void Add(int numberA, int numberB)
            //{
            //    // Note from the declarations that Add is *static* and Result is *not* static

            //    // Y U NO COMPILE??
            //    //Result = numberA + numberB;
            //}

            // Result is a non-static property, it only exists on an instance of
            // the class, not the class type.  We have no instance as we didn't create
            // any "new MathsStatic()", and if we have no instance of MathsStatic there
            // is nowhere to store Result; as Result (non-static) lives on an instance.
            // So where *is* Result?

            MathsStatic ms = new MathsStatic();
            System.Diagnostics.Debug.WriteLine(ms.Result); // There it is :)
            // (but it is 0 as nothing has updated it, this is a new instance of MathsStatic)

            // So ms is now an instance of MathsStatic, and as Result is non-static
            // it exists on the instance (ms).  The Add method is static and exists on the type (MathsStatic).
            // So this code won't compile either

            //ms.Add(1, 2);

            // as "ms" is an instance of MathsStatic so we can only call non-static methods though "ms".
            // The lesson here is that non-static methods of a class can access static resources
            // on that class (however note that they are global), but a static method can't
            // interact with non-static properties of that class

            // An instance can access static properties as even though it is an instance, the type itself
            // still exists and the static property is on that type
            // A static method can't access non-static properties as while the type exists (and therefore the
            // static method) there is no instance of the type in which to store the non-static property

            // A final point, all of my maths classes are standard "public class" classes, so are free to have
            // both static and non-static members and properties.  If you make the class itself as static
            // (public static class) then the class can *only* have static members and properties and you
            // can't create instances of them.  ie you can't do "new MyStaticClass()"
        }
    }
}

The following Code Snippet covers some likely real-world scenarios.  It's really up to you to decide when your code should use static or non-static methods and properties, there is no one-size-fits-all solution to every scenario.  But these are some more real-world examples that will maybe give you some context (after all, we add and subtract using "+" and "-", we don't write maths classes). 


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace WebTest
{
    public partial class StaticReal : System.Web.UI.Page
    {
        // Ok, let's get real!

        public class PersonTypeA
        {
            public int ID { get; set; }
            public string FirstName { get; set; }
            public string LastName { get; set; }

            public string GetFullName()
            {
                return FirstName + " " + LastName;
            }

            public void Load(int id)
            {
                // Don't create sql like this, code is for demonstration purposes only

                string sql = "SELECT First_Name, Last_Name FROM Person WHERE Person_ID = " + id.ToString();

                ID = id;
                // populate properties from the results of the above sql
                // FirstName = ....
            }

            public void Save()
            {
                if (GetFullName().Length > Configuration.MaxNameLength)
                    throw new ApplicationException("Name is too long");

                // Don't create sql like this, code is for demonstration purposes only

                if (ID == 0)
                {
                    string sql = "INSERT INTO Person (First_Name, Last_Name) VALUES ('" + FirstName + "', '" + LastName + "')";
                }
                else
                {
                    string sql = "UPDATE Person SET First_Name = '" + FirstName + "', Last_Name = '" + LastName + "' WHERE Person_ID = " + ID.ToString();
                }
            }
        }

        public class PersonTypeB
        {
            public int ID { get; set; }
            public string FirstName { get; set; }
            public string LastName { get; set; }

            public string GetFullName()
            {
                return FirstName + " " + LastName;
            }
        }

        public static class PersonRepository
        {
            public static void Save(PersonTypeB person)
            {
                if (person.GetFullName().Length > Configuration.MaxNameLength)
                    throw new ApplicationException("Name is too long");

                // Don't create sql like this, code is for demonstration purposes only

                if (person.ID == 0)
                {
                    string sql = "INSERT INTO Person (First_Name, Last_Name) VALUES ('" + person.FirstName + "', '" + person.LastName + "')";
                }
                else
                {
                    string sql = "UPDATE Person SET First_Name = '" + person.FirstName + "', Last_Name = '" + person.LastName + "' WHERE Person_ID = " + person.ID.ToString();
                }
            }

            public static PersonTypeB Load(int id)
            {
                // Don't create sql like this, code is for demonstration purposes only

                string sql = "SELECT First_Name, Last_Name FROM Person WHERE Person_ID = " + id.ToString();

                PersonTypeB p = new PersonTypeB();
                p.ID = id;
                // populate properties from the results of the above sql
                // p.FirstName = ....

                return p;
            }

        }

        protected void Page_Load(object sender, EventArgs e)
        {
            // Method 1

            // Create a PersonTypeA
            PersonTypeA personA = new PersonTypeA();
            personA.FirstName = "Joe";
            personA.LastName = "Bloggs";

            // Save the person
            personA.Save();

            // Load an existing person
            PersonTypeA existingPersonA = new PersonTypeA();
            existingPersonA.Load(1);

            // Method 2

            // Create a PersonTypeB
            PersonTypeB personB = new PersonTypeB();
            personB.FirstName = "Peter";
            personB.LastName = "Smith";

            // Save the person
            PersonRepository.Save(personB);

            // Load an existing person
            PersonTypeB existingPersonB = PersonRepository.Load(1);


            // Method 1 is giving the Person class the ability to do everything itself.  It can store its
            // own properties and load from, and save itself to the database.

            // Method 2 is making sure the Person class only knows about its own properties.  The task of
            // loading and saving the Person is given to a different class (PersonRepository).

            // Method 1 is all non-static...Save acts on that instance of person, Load updates the instance
            // Method 2 uses a non-static object to store state, but a static method of a static class
            // to load and save the state
            // PersonRepository.Save and PersonRepository.Load can be static as they need no object state of
            // their own and they update no object state of their own

            // Which method should you be using?  If it is 1998 or before then use Method 1.  If it is the 21st
            // Century or beyond then use Method 2.  The reason Method 2 is better is a subject for another day :)

            // Finally - up until now you've probably been thinking there is no use for static properties
            // whatsoever as all I've done is warned you of their many dangers.  However, look at both
            // the Save methods to see where we are using them.  The  static Configuration class with
            // static MaxNameLength property is detailed below
        }
    }

    public static class Configuration
    {
        public static int MaxNameLength { get; private set; }

        static Configuration()
        {
            // Here I am hard-coding a value so this might seem useless, and there are obviously better
            // ways of validating the length of a name...but this is just giving you an example of the
            // theory.  If you have data that comes from a resource such as the registry, a config file,
            // the database, a webservice...and that data takes effort to collect but remains unchanged,
            // then you can get it once in a class like this, and as it is static it is global so all
            // classes can use it with the work needed to get the data only done once
            MaxNameLength = 20;

            // There are two tricks at work here.  First...this code we're in is a "static constructor".  It is
            // called the first time any code accesses Configuration.<anything> after your site has started.
            // Subsequent calls to Configuration.<anything> won't run this code as the type has already been
            // loaded into the application domain

            // The second trick is that .net ensures this static constructor is thread-safe.  That means
            // that if, after starting your site, two threads both access Configuration.<anything> for
            // first time at the same moment, this constructor will only run once, the other thread will
            // wait until the constructor is done and use the type as normal.

            // A static constructor is the *only time* it is safe to update static variables without
            // having to think about locking them.  Thanks, Microsoft :)

            // The other quirk to note is that as this code is intended to run when other code accesses
            // this type, a private static constructor makes no sense.  So you will note that there is
            // no public or private modifier on this method, it will always be public.
        }
    }


}
 

No comments:

Post a comment

Thank You for Your Comments. We will get back to you soon.

back to top