#526 – Using Statements Can Alias Nested Namespaces

If you have a type defined in a nested namespace, e.g. type DogLogger defined in the DogLibrary.Utility namespace, you can include a using statements that aliases the inner namespace.

In the example below, we have two using statements.  One provides an alias for the outer DogLibrary namespace, so that you can use types from the DogLibrary namespace by just using the type name.  The second aliases the DogLibrary.Utility namespace, so that you can use types defined in the inner namespace.

using DogLibrary;
using DogLibrary.Utility;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main()
        {
            // DogLibrary.Dog
            Dog d = new Dog();

            // DogLibrary.Utility.DogLogger
            DogLogger logger = new DogLogger();
        }
    }

#525 – Namespaces Can Be Nested

You can define a namespace at the top-level of a source file or you can define a namespace within another namespace.  When you define a namespace within another namespace, you can refer to the inner namespace using a dotted (outer.inner) syntax.

In the example below, we have a Dog class defined within the top-level DogLibrary namespace.  The full name for this type is DogLibrary.Dog.  We also define a Utility namespace within the DogLibrary namespace, and a DogLogger class whose full name is then DogLibrary.Utility.DogLogger.

namespace DogLibrary
{
    public class Dog
    {
    }

    namespace Utility
    {
        public class DogLogger
        {
        }
    }
}

#524 – All Types Within a Namespace Must Be Unique

You can create more than one type with the same name, as long as the exist in different namespaces.  But within a particular namespace, the name of every type must be unique.

In the example below, we declare a Dog class in both the EarthDogs and AlienDogs namespaces.

namespace EarthDogs
{
    public class Dog
    {
        public string Name { get; set; }

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

namespace AlienDogs
{
    public class Dog
    {
        public string Name { get; set; }

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

(In practice, for these classes, you’d probably instead declare a Dog parent class and subclasses EarthDog and AlienDog, which would override the Bark method).

#523 – The using Directive Allows Omitting Namespace

A typical C# program will use types that are defined in a variety of namespaces.  Specifying the fully qualified type name that includes the namespace can become tedious.

using directive tells the compiler what namespaces to look for types in, avoiding the need for fully qualified type names.

Assume that we have a Dog type, defined in the DogLibrary namespace.  The fully qualified type name is DogLibrary.Dog.  But in the code fragment below, we can just use Dog as the type name, because of the using directive.

using DogLibrary;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main()
        {
            Dog kirby = new Dog();
            kirby.Bark();
        }
    }
}

So you can use a type name without its namespace if:

  • The type is defined in the same namespace as the current code
  • using directive for the type’s namespace is present

#522 – The Fully Qualified Name for a Type Includes the Namespace

If we define a Dog type in the DogLibrary namespace, the fully qualified name for the new type is DogLibrary.Dog.  You can use this full name when working with the type.

            DogLibrary.Dog kirby = new DogLibrary.Dog();
            kirby.Bark();

If you’re writing code that exists in the same namespace as a type, you can omit the namespace and just use the short version of the type name.

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

        public void Bark()
        {
            Console.WriteLine("WOOOOOF!");
        }
    }

    public static class DogFactory
    {
        public static Dog MakeDog(string name, int age)
        {
            // Can just use "Dog" as type name
            Dog d = new Dog();
            d.Name = name;
            d.Age = age;
            return d;
        }
    }
}

#521 – Namespaces Help Organize Types

In C#, a namespace is a collection of related types.  The fully qualified name of every type actually includes the namespace that it’s defined in.

You use the namespace keyword to define a namespace.  All types defined within the pair of braces that follow the namespace name then belong to that namespace.

In the example below, the Dog class belongs to the DogLibrary namespace.

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

        public void Bark()
        {
            Console.WriteLine("WOOOOOF!");
        }
    }
}

Because Dog is defined within the DogLibrary namespace, the fully qualifed name of the Dog type is DogLibrary.Dog.

#518 – Splitting the Implementation of a Class Across Multiple Files

It is sometimes convenient to split the source code for a class across more than one source file.  You can do this using the partial keyword.

In the example below, we split the implementation of the Dog class between Dog.cs and Dog-IBark.cs.  The latter file contains the implementation of the IBark interface.  The class definition in both files is marked with the partial keyword.

    // Dog.cs
    public partial class Dog
    {
        public string Name { get; set; }
        public int Age { get; set; }

        /// <summary>
        /// Create new Dog with specified name
        /// </summary>
        /// <param name="name"></param>
        public Dog(string name, int age)
        {
            Name = name;
            Age = age;
        }
    }

 

    partial class Dog : IBark
    {
        public bool CanBark { get; set; }

        public void Bark()
        {
            Console.WriteLine("WOOOOOF!");
        }
    }

Notice that we don’t specify IBark inheritance in the first file, because it does not implement any IBark methods.

#516 – The Assignment Operator is Right-Associative

An expression can contain more than one assignment operator.  If this is the case, the assignments are evaluated from right to left.  Consider the code fragment below.

int x = 12;
int z = 24;
int i = x = z;

Because the assignments are done from right to left, the variable x is first assigned the value that is stored in z (24).  At this point, both x and z have the value of 24.

Next, i is assigned the value that is the result of the first assignment (24).  At this point, x, z and i now all have the value of 24.

Because this behavior can be a little confusing,  it’s generally preferable to do each assignment on a separate line.

int x = 12;
int z = 24;
x = z;
int i = x;

#515 – Binary Operators Are Left-Associative

When an expression contains more than one binary operators, where the operators are identical or have the same precedence, the operators are left-assocative.  This means that the expression is evaluated from left to right.

For example, the result of the expression shown below is 5, rather than 20.  80 is divided by 8 to get an intermediate result of 10.  10 is then divided by 2 to get a result of 5.

            double result = 80 / 8 / 2;

This means that the above expression is equivalent to:

            double result = (80 / 8) / 2;

If you want to force the division of the 2nd and 3rd operands to happen first, you could use parentheses around them:

            // result = 20
            double result = 80 / (8 / 2);

#514 – Examples of Operator Precedence

Each operator has an associated precedence, which indicates the order in which the operators are evaluated when evaluating the  expression.

For example, because multiplicative (*, /, %) operators have a higher precedence than additive (+, -) operators, the multiplication in the expression below happens before the addition, so the answer is 34.

int result = 4 + 5 * 6;

If we want the addition to happen first, we can change the precedence by using parentheses.

// Result = 54
int result = (4 + 5) * 6;

Here are some other examples of operator precedence.

            // Negation operator has higher precedence than conditional operators
            bool res = !false || true;    // true  (negation operator evaluated first)
            res = !(false || true);       // false (conditional OR evaluated first)

            // && has higher precedence than ||
            bool res = true || false && false;    // true
            res = (true || false) && false;       // false

Follow

Get every new post delivered to your Inbox.

Join 37 other followers