A field is like a variable that we declare at the class level. We use that to store data about the class. Suppose we have a class that represents a Customer. Customers have a list of Orders. Notice that in our code we have a customer class and an Orders class and inside the customers class we have declared a list of orders. A class containing a list of classes? Of course that’s fine. After all, everything is a class and we’ve being doing this since the beginning of our variable declarations. So we have a class that has a class. This is called composition.
Within a class definition, you define all members of the class, including field, methods and properties. Fields are defined using a standard variable definition format, with optional initialization, along with the access modifiers (public, private, internal, protected). Public fields in the .NET framework are named using PascalCasing, rather than camelCasing. For private fields, there is no strong recommendation, so camel Casing can be used. Mosh Hamedani recommends using the underscore camelCase way of declaring private fields. For more information on private, see the post called C# Access Modifiers and Encapsulation.
Fields can also have the keyword readonly, meaning that the field can be assigned a value only during constructor execution or by an internal assignment. For an example of this have a look at the post called C# Composition.
Static
We have a separate post on static fields called C# Static Fields.
Initialization of Fields
You can initialize fields using constructors. You might use the default constructor. You can also directly initialize the field without the use of a constructor. One advantage of initialization of fields directly is that no matter which constructor is called, the field will always be initialized. For example, if you have a field that is a list, it will be initialized to an empty list without the use of a constructor.
Readonly Modifier
We can use the readonly modifier to ensure that it is only assigned a value one time. Readonly creates a level of safety in our application.
There is a lot of pieces in this code below. You need to be familiar with the basics of classes, constructors, methods, lists and the this keyword. We have our two objects, Customers and Orders. Customers have many orders. In the Customers object we have created a list of Orders. Let’s talk about constructors. We need to initialize Orders with an empty list. Whenever you have a class, such as Customer, and inside that class you have a List, you should always initialize that list to an empty list.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ClassFields { class Program { public class Order { public int Id; } public class Customer { public int Id; public string Name; public Order myOrder = new Order(); public List<Order> Orders = new List<Order>(); // alternative to the above initialization is to initialize in default constructor: // public Customer() { Orders = new List<Order> } // both work, it is a matter of preference, but be consistent // no parameterless constructor here! public Customer(int id) { this.Id = id; } public Customer(int id, string name) : this(id) { // constructor this.Name = name; } public void Promote() // our newly created method { // introduce a BUG - we shouldn't be reinitializing here. Orders = new List<Order>(); // with readonly, you get a compiletime error. Good! } } static void Main(string[] args) { var myInts = new List<int>() {1,2,3,4}; // just an example of lists myInts.Add(1); // var customer new Customer(); // above is ERROR: Customer does not contain a constructor that takes zero arguments var customer = new Customer(1); customer.Orders.Add(new Order()); customer.Orders.Add(new Order()); Console.WriteLine(customer.Orders.Count); // we get 2 Console.ReadKey(); } } }
Introduce a Bug
Let’s make the case for using readonly. Suppose you have another method that is used to promote a certain customer to a Gold customer from a Silver one. We create a new method called Promote(). Suppose another programmer in the group is working on this code.
Fix the Bug
Use readonly to fix the bug like this.
public readonly List<Order> Orders = new List<Order>();
Below is our bug-free code. On the console we get 2 and 2.
class Program { public class Order { public int Id; } public class Customer { public int Id; public string Name; //public Order myOrder = new Order(); public readonly List<Order> Orders = new List<Order>(); // no parameterless constructor here public Customer(int id) { this.Id = id; } public Customer(int id, string name) : this(id) { // constructor this.Name = name; } public void Promote() // method { // introduce a BUG in the line below. // Orders = new List<Order>(); // with readonly, you get a compiletime (not runtime) error. Good! } } static void Main(string[] args) { var myInts = new List<int>() {1,2,3,4}; // just an example of lists myInts.Add(1); // var customer new Customer(); // ERROR: Customer does not contain a constructor that takes zero arguements var customer = new Customer(1); customer.Orders.Add(new Order()); customer.Orders.Add(new Order()); Console.WriteLine(customer.Orders.Count); // we get 2 customer.Promote(); Console.WriteLine(customer.Orders.Count); // we get 0 Console.ReadKey(); } }
Because instance fields are members of a class, all instance fields are stored in the heap, regardless of whether they’re value types or reference types.