C# Interface Properties
In C# interfaces, properties provide a powerful way to define getters and setters that implementing classes must provide. They are an essential part of interface design, allowing you to specify read-only, write-only, or read-write behaviors without dictating the implementation details.
Introduction to Interface Properties
Interface properties define a contract that implementing classes must fulfill. They specify what properties a class must expose, but not how those properties should work internally. Properties in interfaces act similarly to methods but use property syntax for more intuitive data access.
Since C# 8.0, interfaces can also include default implementations for properties, making them even more versatile.
Basic Syntax for Interface Properties
Here's the basic syntax for defining properties in an interface:
interface IMyInterface
{
// Property declaration
Type PropertyName { get; set; }
}
The implementing class must provide the property with the same name, type, and accessibility modifiers.
Types of Interface Properties
Read-only Properties
Read-only properties only have a getter:
interface IReadOnlyExample
{
string Name { get; }
}
class Employee : IReadOnlyExample
{
public string Name { get; private set; }
public Employee(string name)
{
Name = name;
}
}
In this example, the implementing class must provide a way to read the Name
property but can control how it's set internally.
Write-only Properties
Write-only properties only have a setter:
interface IWriteOnlyExample
{
string Password { set; }
}
class User : IWriteOnlyExample
{
private string _password;
public string Password
{
set { _password = value; }
}
public bool ValidatePassword(string attempt)
{
return _password == attempt;
}
}
Write-only properties are less common but useful for cases like passwords or other sensitive information.
Read-write Properties
Read-write properties have both getters and setters:
interface IPerson
{
string FirstName { get; set; }
string LastName { get; set; }
int Age { get; set; }
}
class Student : IPerson
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
}
Auto-implemented Properties in Implementing Classes
When implementing interface properties, you can use auto-implemented properties for clean, concise code:
interface IProduct
{
int ProductId { get; set; }
string Name { get; set; }
decimal Price { get; set; }
}
class Book : IProduct
{
// Auto-implemented properties
public int ProductId { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
// Additional properties specific to Book
public string Author { get; set; }
public int Pages { get; set; }
}
Custom Implementation of Interface Properties
You can also provide custom implementations with backing fields:
interface ITemperature
{
double Celsius { get; set; }
double Fahrenheit { get; set; }
}
class TemperatureConverter : ITemperature
{
private double _celsius;
public double Celsius
{
get { return _celsius; }
set { _celsius = value; }
}
public double Fahrenheit
{
get { return (_celsius * 9 / 5) + 32; }
set { _celsius = (value - 32) * 5 / 9; }
}
}
// Usage example:
static void Main()
{
TemperatureConverter temp = new TemperatureConverter();
// Set Celsius, read both
temp.Celsius = 25;
Console.WriteLine($"Celsius: {temp.Celsius}°C, Fahrenheit: {temp.Fahrenheit}°F");
// Output: Celsius: 25°C, Fahrenheit: 77°F
// Set Fahrenheit, read both
temp.Fahrenheit = 50;
Console.WriteLine($"Celsius: {temp.Celsius}°C, Fahrenheit: {temp.Fahrenheit}°F");
// Output: Celsius: 10°C, Fahrenheit: 50°F
}
This example shows a custom implementation where changing one property automatically updates the related property through conversion.
Default Interface Properties (C# 8.0+)
Starting with C# 8.0, interfaces can include default implementations for properties:
interface IShape
{
double Area { get; }
double Perimeter { get; }
// Default property with implementation
string Description => $"Shape with area {Area} and perimeter {Perimeter}";
}
class Circle : IShape
{
public double Radius { get; }
public Circle(double radius)
{
Radius = radius;
}
public double Area => Math.PI * Radius * Radius;
public double Perimeter => 2 * Math.PI * Radius;
// No need to implement Description as it uses the default implementation
}
// Usage:
static void Main()
{
Circle circle = new Circle(5);
Console.WriteLine(circle.Description);
// Output: "Shape with area 78.54... and perimeter 31.42..."
}
Interface Properties vs. Abstract Properties
It's important to understand the difference between interface properties and abstract properties:
// Interface property
interface IVehicle
{
int Speed { get; set; }
}
// Abstract property
abstract class Vehicle
{
public abstract int Speed { get; set; }
}
Key differences:
- Interfaces can be implemented by any class, but a class can only inherit from one abstract class
- Abstract classes can include implementation details, while traditional interfaces only define the contract
- Abstract classes can have constructors and fields, which interfaces cannot
Explicit Interface Implementation
Sometimes you might want to implement an interface property explicitly:
interface ILoggable
{
string LogData { get; }
}
interface IPrintable
{
string LogData { get; } // Same property name as in ILoggable
}
class Report : ILoggable, IPrintable
{
// Explicit interface implementation
string ILoggable.LogData => "Detailed log data for logging purposes";
string IPrintable.LogData => "Formatted data for printing";
// Regular properties
public string Title { get; set; }
}
// Usage example:
static void Main()
{
Report report = new Report();
report.Title = "Annual Report";
// Need to cast to access explicit implementation
Console.WriteLine(((ILoggable)report).LogData);
// Output: "Detailed log data for logging purposes"
Console.WriteLine(((IPrintable)report).LogData);
// Output: "Formatted data for printing"
}
Explicit implementation helps resolve naming conflicts and keeps the class's public API clean.
Real-world Example: Building a Content Management System
Let's see how interface properties can be used in a simple content management system:
// Base interfaces
interface IContent
{
string Title { get; set; }
string Author { get; set; }
DateTime PublishDate { get; set; }
bool IsPublished { get; set; }
}
interface ICommentable
{
List<string> Comments { get; }
void AddComment(string comment);
}
interface IVersionable
{
int Version { get; }
void UpdateVersion();
}
// Implementation
class Article : IContent, ICommentable, IVersionable
{
private int _version = 1;
// IContent implementation
public string Title { get; set; }
public string Author { get; set; }
public DateTime PublishDate { get; set; }
public bool IsPublished { get; set; }
// ICommentable implementation
public List<string> Comments { get; } = new List<string>();
public void AddComment(string comment)
{
Comments.Add(comment);
}
// IVersionable implementation
public int Version => _version;
public void UpdateVersion()
{
_version++;
}
// Additional functionality
public string Content { get; set; }
}
// Usage example:
static void Main()
{
Article article = new Article
{
Title = "Understanding C# Interfaces",
Author = "Jane Developer",
PublishDate = DateTime.Now,
IsPublished = true,
Content = "Interfaces in C# provide a powerful way to..."
};
// Using the commentable feature
article.AddComment("Great article!");
article.AddComment("Very helpful explanation.");
// Using the versionable feature
article.UpdateVersion();
Console.WriteLine($"Article: {article.Title} by {article.Author}");
Console.WriteLine($"Published: {article.PublishDate}, Version: {article.Version}");
Console.WriteLine($"Comments ({article.Comments.Count}):");
foreach (var comment in article.Comments)
{
Console.WriteLine($"- {comment}");
}
// Output:
// Article: Understanding C# Interfaces by Jane Developer
// Published: [current date/time], Version: 2
// Comments (2):
// - Great article!
// - Very helpful explanation.
}
This example shows how interfaces with properties help model a system with clear separation of concerns and flexible implementation.
Best Practices for Interface Properties
-
Keep interfaces focused: Each interface should represent a single responsibility or capability.
-
Use read-only properties where applicable to enforce immutability:
csharpinterface IReadOnlyCustomer
{
int Id { get; }
string Name { get; }
} -
Consider default implementations (C# 8.0+) for calculated properties:
csharpinterface IPerson
{
string FirstName { get; set; }
string LastName { get; set; }
string FullName => $"{FirstName} {LastName}";
} -
Document expected behavior, especially for properties with complex semantics:
csharpinterface ICacheable
{
/// <summary>
/// Time in seconds that the item should remain in cache.
/// Value of 0 means no caching, -1 means cache indefinitely.
/// </summary>
int CacheDuration { get; set; }
}
Common Pitfalls and Solutions
Pitfall 1: Conflicting Property Signatures
When multiple interfaces define the same property but with different types or accessibility:
interface IA
{
string Value { get; set; }
}
interface IB
{
int Value { get; set; } // Same name but different type
}
// This won't compile!
class MyClass : IA, IB
{
public string Value { get; set; } // Can't satisfy both interfaces
}
Solution: Use explicit interface implementation:
class MyClass : IA, IB
{
// Explicitly implement both interfaces
string IA.Value { get; set; }
int IB.Value { get; set; }
}
Pitfall 2: Interface Properties vs. Class Access Modifiers
An interface property might need different access levels in the implementing class:
interface IConfigurable
{
string ConnectionString { get; set; }
}
class DatabaseService : IConfigurable
{
// Need to make this publicly accessible to satisfy the interface
// but want to restrict who can set it
public string ConnectionString { get; private set; }
public DatabaseService(string connectionString)
{
ConnectionString = connectionString;
}
}
Summary
Interface properties are a powerful feature in C# that allow you to:
- Define contracts for data access that implementing classes must follow
- Specify read-only, write-only, or read-write behaviors
- Create flexible designs with clear separation of concerns
- Enable polymorphism through interface types
The ability to define properties in interfaces makes C# interfaces particularly expressive and useful for designing clean, maintainable systems. With C# 8.0's default interface members, interfaces become even more powerful by allowing you to provide default implementations for properties.
Exercises
-
Create an interface called
IEmployee
with properties forName
,Id
, andSalary
. Implement this interface in a class calledFullTimeEmployee
. -
Design an interface hierarchy for a file system, with interfaces like
IFile
(with properties likeName
,Size
,CreationDate
),IDirectory
, etc. Implement these interfaces for a simple in-memory file system. -
Create an interface with a property that has a default implementation (C# 8.0+). Override this default in an implementing class.
-
Design an interface with both read-only and read-write properties, then implement it in a class that adds appropriate validation logic.
Additional Resources
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)