Code Blocks
image by Kelvin Gabeci

Code Blocks

Introduction

When we use decision and iteration statements, we rely on code blocks to define units of code that should be skipped, executed, iterated over, and so on. However, these code blocks also impact variable declaration and accessibility. And code blocks are used in higher-level constructs when we start building real applications.

Code blocks and variable scope

A code block is one or more code statements that define an execution path. Typically the statements outside of the code block affect when, if and how often that block of code is executed at run time.?

Code blocks also affect variable scope. Variable scope is the visibility of the variable to the other code in your application. A locally scoped variable is only accessible inside of the code block in which it’s defined. If you attempt to access the variable outside of the code block, you’ll get a compiler or runtime error.

Example:?

Create a variable inside of a code block

Add the following code:

bool flag = true;
if (flag)
{
  int value = 10;
  Console.WriteLine($"Inside of code block: {value}");
}        


Now, run the code. You should see the following output.

Inside of the code block: 10

This is as expected. But what if we want to access the variable value outside of if statement’s code block?

Below the above code, add the following line of code:

Console.WriteLine($"Outside of code block: {value}");        

So, the entire code example should look like this:

bool flag = true; 
if (flag)
{
  int value = 10;
  Console.WriteLine("Inside of code block: " + value);
}
Console.WriteLine($"Outside of code block: {value}");        

This time, when you attempt to run the application, you’ll get a compilation error:

error CS0103: The name ‘value’ does not exist in the current context

The problem is that a variable defined in a code block is only accessible (or rather, visible) within that code block. The variable isn’t accessible outside of the code block in which it’s defined.

A local variable is a variable defined in a method code block. We’ll talk about method code blocks much later.

To access a variable from both an outer and inner code block (like the if statement’s code block), you’ll need to move the variable declaration outside of the if statement’s code block so that all the code has visibility to that variable.

Modify your code as follows:

bool flag = true;
int value;
if (flag)
{
  value = 10;
  Console.WriteLine("Inside of code block: " + value);
}
Console.WriteLine("Outside of code block: " + value);        

This time, when you attempt to run the application, you’ll get a compilation error:

error CS0165: Use of unassigned local variable ‘value’

This is a simple problem to fix, however it gives us another insight into working with code blocks.

If the line of code value = 10; were outside of the if statement’s code block, the compiler would compile our application. However, since that line of code is inside the if statement’s code block, there’s a possibility that the variable will never be assigned a value, which the compiler won’t allow.

To fix the “unassigned local variable” issue, we need to initialize the variable with a value. Update variable declaration to include an initialization.

bool flag = true;
int value = 0;
if (flag)
{
  value = 10;
  Console.WriteLine("Inside of code block: " + value);
}
Console.WriteLine("Outside of code block: " + value);        

Now, when you run the application, you should see the following output.

Inside of code block: 10 Outside of code block: 10

Recap

Here are a few important things to remember about code blocks:

1. When you define a variable inside of a code block, its visibility is local to that code block and inaccessible outside of that code block.

2. To make a variable visible inside and outside of a code block, you must define the variable outside of the code block.

3. Don’t forget to initialize any variables whose value is set in a code block, such as an if statement.

Code blocks define methods, classes, and namespaces

Code blocks are critical to defining higher-level structures for our code. If you were to create a new C# Console Application project, a file name named Program.cs is generated that contains code from a template. Here’s the generated code from an application?

named MyNewApp:
using System;
namespace MyNewApp
{
  class Program
  {
    static void Main(string[] args)
    {
      Console.WriteLine("Hello World!");
    }
  }
}        

Here you see three levels of code blocks, starting from the inner-most code block and working our way outward:

a method Main()

a class Program

a namespace MyNewApp

As you build real applications, you’ll write methods, and organize them into classes and namespaces.

A method is a code block that is a unit of execution. In other words, once the method is called by its name, the entire method will execute until, the runtime encounters the return keyword, or the runtime encounters an exception and can’t continue, or the runtime successfully executes each line of code in the method.

The method’s name Main() is special. When the program is executed, by default the?.NET Runtime will search for a method named Main() to use as the starting point, or entry point, for the program.

A class is a container for members like methods, properties, events, fields, and so on. In previous lessons, you learned that you must create an instance of a class using the new keyword if you want to call a method that requires state (or rather, instance methods).?

A namespace disambiguates class names. There are so many classes in the?.NET Class Library and it’s possible to have two classes with the same name. The namespace ensures you can instruct the compiler which class and method you want to work with by also specifying a namespace.

When you create a new project it will automatically create a namespace using the project’s name. That’s why the default namespace in the code example above is MyNewApp. You can create additional namespaces in your code as needed, and can create a hierarchical series of namespaces by using the dot operator. So, suppose we wanted to create a second level of namespaces for the classes of our application. We could add a child-namespace like so:

namespace MyNewApp.Business
  { // Classes here } And perhaps another child-namespace like so:
namespace MyNewApp.Data 
  { // Classes here }        

In the MyNewApp.Business namespace, we would expect to add classes that implemented the business logic of our application. In the MyNewApp.Data namespace, we would expect to add classes that implemented the data access features of our application. We can add as many namespaces as we want. We can create namespaces as many levels deep as we need. We would just keep appending child-namespace names using the dot operator.

Most importantly for the purposes of this lesson, code blocks define the boundaries for each of these constructs. Code blocks suggest an ownership, or containment, relationship. So, the rules we just learned about variable scope and access are true at this level as well.

Calling a method in the same class

The following code example calls a method named Reverse() that is defined in the same class.

using System;
namespace MyNewApp
  {
    class Program
      {
        static void Main(string[] args)
          {
            string value = "C# programming";
            string reversedValue = Reverse(value);
            Console.WriteLine($"Secret message: {reversedValue}");
          }

        static string Reverse(string message)
          {
            char[] letters = message.ToCharArray();
            Array.Reverse(letters);
             return new string(letters);
          }
      }
   }        

Focus on the line of code that calls the Reverse() method:

string reversedValue = Reverse(value);

Since the Reverse() method is defined in the same class, the code that calls the method doesn’t need to qualify the method’s name with the class name.

Calling a method from a different class

What if we move the Reverse() method into its own class? Consider the following code example.

using System;
namespace MyNewApp {
  class Program {
    static void Main(string[] args) {
      string value = "C# Programming";
      string reversedValue = Utility.Reverse(value);
      Console.WriteLine($"Secret message: {reversedValue}");
    }
  }
  class Utility {
    public static string Reverse(string message) {
      char[] letters = message.ToCharArray();
      Array.Reverse(letters); return new string(letters);
    }
  }
}        

First, focus on these lines of code:

class Utility {
  public static string Reverse(string message) {
  }
}        

Here, we define the Utility class and add the Reverse() method. We also add the public keyword. Otherwise, it would be inaccessible to the Program class’ Main() method. Without the public keyword, we would see the compilation error:

‘Utility.Reverse(string)’ is inaccessible due to its protection level

We’ll talk about accessibility modifiers like public and private in other lessons.

Next, focus on how we needed to use the new Utility class’ name when accessing the Reverse() method from the Main() method.

string reversedValue = Utility.Reverse(value);

Since the method no longer “lives” in the same class, to call the Reverse() method, we have to access it via the class it was moved into.

Referencing a class in a different namespace?

What if we move the Utility class into a different namespace? How will this affect the accessibility of the class and its method? Consider the following code example.

using System;
namespace MyNewApp {
  class Program {
    static void Main(string[] args) {
      string value = "C# Programming";
      string reversedValue = MyNewApp.Utilities.Utility.Reverse(value);
      Console.WriteLine($"Secret message: {reversedValue}");
    }
  }
}
namespace MyNewApp.Utilities {
  class Utility {
    public static string Reverse(string message) {
      char[] letters = message.ToCharArray();
      Array.Reverse(letters); return new string(letters);
    }
  }
}        

First, focus on the new namespace we added.

namespace MyNewApp.Utilities

Not only did we create a new namespace MyNewApp.Utilities, but we also moved our Utility class and its contents into the new namespace.

Second, focus on how we called the Reverse() method.

string reversedValue = MyNewApp.Utilities.Utility.Reverse(value);

Notice that we used the “full name” of the class, including its full namespace.

Since the child-namespace Utilities and the Program class are both defined in the MyNewApp parent namespace, we could avoid using the namespace in this instance. The following code illustrates this.

string reversedValue = Utilities.Utility.Reverse(value);

Even though it’s less verbose, many would consider this hard to read because we typically don’t separate parent and child namespaces. Consider keeping the full namespace together whenever it’s needed to promote clarity and readability.

The using statement helps the compiler resolve namespaces, but requiring fewer keystrokes

A better option would be to add a using statement. The using statement is added to the top of a code file. It resolves the class names that are used in the file, instructing the compiler to look at the list of namespaces to find all of the class names.

Here’s another version of the application that adds a using statement to simplify the call to the Reverse() method.

using System;
using MyNewApp.Utilities;
namespace MyNewApp {
  class Program {
    static void Main(string[] args){
      string value = "C# Programming";
      string reversedValue = Utility.Reverse(value);
      Console.WriteLine($"Secret message: {reversedValue}");
    }
  }
}
namespace MyNewApp.Utilities {
  class Utility {
    public static string Reverse(string message) {
      char[] letters = message.ToCharArray();
      Array.Reverse(letters);
      return new string(letters);
    }
  }
}        

Focus on this line of code, added on the second line:

using MyNewApp.Utilities;

The using statement tells the compiler to look here when attempting to resolve any class names it needs to find. Now, we can call the Reverse() method using only the class name like so:

string reversedValue = Utility.Reverse(value);

This is also why the template that generates the Program.cs file includes this line of code at the top:

using System;

It makes it possible to call Console.WriteLine() instead of System.Console.WriteLine().

What does this have to do with code blocks?

Code blocks define the boundaries of higher-level structures like namespaces, classes, and methods, just as they define the boundaries of decision and iteration statements. These boundaries require consideration because they affect the visibility of both variables and other larger structures like methods and classes. In some cases, additional keywords like public and using must be used to pass into the boundaries of another block of code. In other cases, structures defined as siblings inside the same code block can reference each other freely.

Recap

1. Code blocks define higher-level structures like namespaces, classes, and methods.

2. Just as code blocks affect the visibility of variables defined inside of lower-level structures like decision and iteration statements, code blocks also affect the visibility of methods between classes, and classes between namespaces.

3. The using statement can be added to code files to instruct the compiler where to look for references to classes.

?Removing code blocks in if statements

Software developers love when they can write code that saves keystrokes and visual space without sacrificing readability. In some cases, less is more?—?as long as it makes the code more readable and understandable.

If the code block needs only one line of code, chances are you don’t need to define a formal code block using curly braces, nor do you need to separate your code into multiple lines. Making these changes is merely stylistic and shouldn’t affect the functionality at all although it might impact how readable the code is. You may try to remove the curly braces and white space, then decide to revert if you find that the code is less readable.

Add the following code to the code window.

bool flag = true;
if (flag) {
 Console.WriteLine(flag);
}        

Now, run the code. You should see the following output.

True

This is a good starting point. We have a code block with one line of code. But in this case, is defining a code block necessary? Since we’re merely executing a single line of code if the flag is true, we can remove the curly braces.

bool flag = true;
if (flag) Console.WriteLine(flag);        

If you run the code, the output will be the same. However, there’s two fewer lines of code.

Since both the if statement and the method call to Console.WriteLine() are short, we could choose to combine them to a single line.

bool flag = true; if (flag) Console.WriteLine(flag);        

If you run the code, the output will be the same. However, there’s now three fewer lines of code than when we started.

string name = "steve";
if (name == "bob") Console.WriteLine("Found Bob");
else if (name == "steve") Console.WriteLine("Found Steve");
else Console.WriteLine("Found Chuck");        

When you run the code, it should produce the following output.

Found Steve

These lines of code look dense and hard to read. You may want to reformat the code to include a line break after the if, else if, and else statements.

string name = "steve";
if (name == "bob") Console.WriteLine("Found Bob");
else if (name == "steve") Console.WriteLine("Found Steve");
else Console.WriteLine("Found Chuck");        

However, when you need to perform a comparison around a single value with multiple possible options, you may want to consider using the switch statement instead. We cover the switch statement before long.

Recap

Here are a few important things to remember about code blocks:

1. If you realize you only have one line of code in a code block, you can remove the curly braces and white space.

2. Only remove code blocks when it makes the code more readable.

3. Only remove the line feed if it makes the code more readable.

Summary

Our goal was to understand how code blocks impact lower-level and higher-level code constructs, and take the appropriate action to ensure that our variables, methods, and classes were visible as needed.


要查看或添加评论,请登录

社区洞察

其他会员也浏览了