Fields and Properties
Data within a class is primarily managed through fields, which are typically declared as private to restrict external access. Fields hold the actual values. To expose data safely to the outside, properties are used instead of public fields. Properties act as gateways to private fields, desrcibing an object's static characteristics. Assigning or reading a property manipulates the underlying private field. Properties can enforce validation logic and be restricted to read-only (omitting the set accessor) to enhance security. Auto-implemented properties offer concise syntax when no custom logic is required, though they prevent direct field access, custom read/write restrictions, and validation.
Methods
Methods define the dynamic behaviors of an object—what it can do. These can be instance methods or static methods (alongside constructors and virtual/abstract methods for polymorphism). Method names should be verbs or verb-object phrases with an initial capital letter. Unspecified access modifiers default to private. Method parameters are defined as needed. Local variables exist within method scopes and are cleared upon execution completion, whereas member variables (fields) persist across method calls and are cleared unpredictably by the garbage collector.
Encapsulation Encapsulation bundles smaller components into a larger, cohesive unit, concealing internal details to ensure security and reduce complexity. A property encapsulates a field; a method encapsulates logic; a class encapsulates properties and methods; a module encapsulates classes. Public members serve as the interface, while private ones hide implementation, achieving high cohesion and low coupling. This abstraction allows developers to consume functionality without worrying about internal mechanics, promoting code reusability.
Method Overloading Overloading enables multiple methods to share the same name while differing in parameter count or types. The compiler resolves the correct version based on the arguments provided. This simplifies the class interface and lowers cognitive load for callers. The return type is irrelevant to overloading constraints. Static methods also support overloading.
Static Members
The static keyword modifies classes, methods, or fields, making them belong to the type itself rather than an instance. Static members load into memory upon program execution and persist until the application terminates. While convenient for frequently used utilities, excessive use is discouraged. Crucially, static members cannot directly invoke instance members. Constructor overloading often uses the :this() syntax to chain calls and avoid duplicate initialization code.
Object Initialization and Destruction Beyond constructors, C# supports object initializers to set properties inline during instantiation:
var person = new Person()
{
Id = 1001,
DateOfBirth = DateTime.Parse("1990-5-14"),
FullName = "Alice"
};
Unlike constructors, initializers lack enforcement, cannot execute complex logic (like file I/O), and only handle property assignment. Constructors define mandatory setup logic within the class. Objects have a lifecycle: referenced (active) or floating (unreferenced but occupying memory). The .NET Garbage Collector (GC) automatically reclaims floating objects, eliminating manual memory management. Destructors (~ClassName()) exist but are largely redundant in managed environments compared to C++.
Value and Reference Types
C# divides data into value types (e.g., int, double, bool) and reference types (e.g., arrays, objects). Strings are technically reference types but behave like values due to immutability. Assigning a value type copies the data, whereas assigning a reference type copies the memory address. Modifying a reference through a new variable affects the original:
var user1 = new User { Name = "John" };
var user2 = user1;
user2.Name = "Jane"; // user1.Name is now "Jane"
To pass value types by reference, the ref keyword forces address passing. The out keyword similarly passes by reference but primarily signals that the method will assign a value, enabling multiple return parameters. Both should be used sparingly, favoring dictionaries or tuples for multiple returns.
Deep Copying When a distinct copy of a reference type is required, deep copying clones both the structure and the data, yielding independent memory locations. Here is an implementation using data serialization:
public static T CreateClone<T>(T source)
{
if (source == null) return default;
using (var stream = new MemoryStream())
{
var serializer = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
serializer.Serialize(stream, source);
stream.Seek(0, 0);
return (T)serializer.Deserialize(stream);
}
}
Base Class and Boxing
All C# types implicitly inherit from System.Object. Converting a value type to object is called boxing (object val = 42;), while reversing it is unboxing (int num = (int)val;). Frequent boxing/unboxing degrades performance. The protected modifier restricts field access to the defining class and its derived types. A derived class instance can access protected members defined in its base, but cannot access them through an instance of the base class itself.