Including both a C# class (like the Car
class) and logic for interacting with users in one file isn't best practice. Let's refactor our code and address the proper file structure for a C# application.
C# uses namespaces to group interrelated and interdependent classes together. A namespace is a keyword used to identify sets of classes. Packaging sets of classes in namespaces helps us write clean, DRY code. If two classes are in the same namespace, they will 'know' about each other and be in the same scope.
To give a class a namespace, we simply wrap the class in another set of curly braces and declare the namespace like this:
namespace ExampleNameSpace
{
public class ExampleClass
{
// all ExampleClass methods, fields, and properties here
}
}
We can wrap other classes in the same namespace to make them available to each other:
namespace ExampleNameSpace
{
public class SecondClass
{
// all SecondClass methods, fields, and properties here
}
}
Let's follow this pattern in our car dealership application.
Instead of including everything in a single file, we need to separate our code into two files:
One will contain the user interface or command line interface logic. This file will be responsible for prompting the user, gathering user input, calling any methods, and returning information to the user. This is the "frontend" or "user interface logic." We will name this file Program.cs
.
The other will contain our custom class. This includes the class declaration itself, our constructor, and any additional methods. We'll refer to this as the class file or business logic. This is the "backend" of our application. This is our Car.cs
file.
Let's separate our code into the appropriate files now.
car-dealership
.CarDealership.csproj
in this directory. See the code snippet below for the exact code that should be in this file.Car.cs
file in this directory.Program.cs
.public class Program
block of code out of Car.cs
, and place it inside Program.cs
.using System
and using System.Collections.Generic;
lines to the top of Program.cs
too.using System.Collections.Generic;
line from Car.cs
, because it no longer contains any List
objects.Program
class in a new namespace that represents our project, Dealership
.Car
class in a namespace that represents its relationship to our project, Models
. Note that the word Models
here does not have to do with a car's model! In our projects we refer to the classes that we utilize, such as our Car
class, as Model
s, because they model the format of the objects we will create. Regardless of the theme of your project, your custom classes that you call in the frontend are known as Model
s.The result should be three files that look like this:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
</Project>
using System;
using System.Collections.Generic;
namespace Dealership
{
public class Program
{
public static void Main()
{
Car volkswagen = new Car("1974 Volkswagen Thing", 1100, 368792);
Car yugo = new Car("1980 Yugo Koral", 700, 56000);
Car ford = new Car("1988 Ford Country Squire", 1400, 239001);
Car amc = new Car("1976 AMC Pacer", 400, 198000);
List<Car> Cars = new List<Car>() { volkswagen, yugo, ford, amc };
Console.WriteLine("Enter maximum price: ");
string stringMaxPrice = Console.ReadLine();
int maxPrice = int.Parse(stringMaxPrice);
List<Car> CarsMatchingSearch = new List<Car>(0);
foreach (Car automobile in Cars)
{
if (automobile.WorthBuying(maxPrice))
{
CarsMatchingSearch.Add(automobile);
}
}
foreach(Car automobile in CarsMatchingSearch)
{
Console.WriteLine(automobile.MakeModel);
}
}
}
}
namespace Models
{
public class Car
{
public string MakeModel;
public int Price;
public int Miles;
public Car(string makeModel, int price, int miles)
{
MakeModel = makeModel;
Price = price;
Miles = miles;
}
public bool WorthBuying(int maxPrice)
{
return (Price <= maxPrice);
}
}
}
When we separate our logic into different files, we launch our program by compiling and launching the file containing the Main()
method. Remember that Main()
is the entry point in our program. It's the method that runs first. We can test our new program structure by navigating into our car-dealership
directory and executing the following command:
$ dotnet run
...But wait! We get an error!
Program.cs(8,5): error CS0246: The type or namespace name `Car' could not be found. Are you missing an assembly reference?
Program.cs(9,5): error CS0246: The type or namespace name `Car' could not be found. Are you missing an assembly reference?
Program.cs(10,5): error CS0246: The type or namespace name `Car' could not be found. Are you missing an assembly reference?
Program.cs(11,5): error CS0246: The type or namespace name `Car' could not be found. Are you missing an assembly reference?
Program.cs(13,10): error CS0246: The type or namespace name `Car' could not be found. Are you missing an assembly reference?
Program.cs(19,10): error CS0246: The type or namespace name `Car' could not be found. Are you missing an assembly reference?
Program.cs(21,32): error CS0841: A local variable `Cars' cannot be used before it is declared
Program.cs(29,31): error CS0841: A local variable `CarsMatchingSearch' cannot be used before it is declared
Compilation failed: 8 error(s), 0 warnings
Cannot open assembly 'Program.exe': No such file or directory.
Our compilation fails because the Program
class no longer knows about the Car
class. This is because they're now in separate files and separate namespaces. So how do we tell the Program
class that the Car
class exists? We need to use its namespace.
It's time to explore the using
directive further. We'll add using Models
to our Program.cs
file:
using System;
using System.Collections.Generic;
using Models; //<-----This is new code.
...
The keyword using
is called a directive. It tells C# that the code in this file will be "using" code that isn't contained in this file or in this file's namespace. Our using
statements import other namespaces that contain the code our file needs. For example, we need to import System
so we can use the Console
class. We can also import custom namespaces.
If classes in different files share namespaces, it's not necessary to import namespaces with the using
directive. For instance, if class A
in file A.cs
is in the same namespace as class B
in file B.cs
, file A.cs
does not need a using B
directive. Classes in the same namespace have access to each other as long as they both exist within the same directory tree.
It is important to note that a using [namespace]
directive imports the types contained in the given namespace, but specifically does not import nested namespaces. This is to avoid naming conflicts and explains why we must import both System
to use Console
and System.Collections.Generic
to use List
s even though the latter namespace is nested in the former.
namespace
s and Handling Multiple FilesWhen we create namespaces, we want to make sure they tell us something useful by naming them in a descriptive manner. A convention in C# is to use our directory structure to define our namespaces.
We define Car()
within the Models
namespace, but the Models
are part of our Dealership
project. Let's rename that namespace to Dealership.Models
and update the corresponding using directive:
...
namespace Dealership.Models
...
using Dealership.Models;
To organize our code further, we'll create a Models
folder that holds our back-end logic. It should hold all classes we use for storing, manipulating, and retrieving data. Having a Models
folder is considered best practice.
Here's our new project structure:
car-dealership ├── Models │ └── Car.cs └── Program.cs
We do not need to make any updates to our using
directive for the program to run in the terminal.
To read more about the using
directive, check out the official Microsoft documentation.
namespace ExampleNameSpace
{
public class ExampleClass
{
// all ExampleClass methods and properties here
}
}
We can wrap other classes in the same namespace to make them available to each other:
namespace ExampleNameSpace
{
public class SecondClass
{
// all SecondClass methods and properties here
}
}
Lesson 4 of 10
Last updated more than 3 months ago.