C Sharp 只读属性
外观
C#只读属性[编辑 | 编辑源代码]
介绍[编辑 | 编辑源代码]
在C#中,只读属性(Read-only Property)是一种特殊类型的属性,它允许外部代码获取属性的值,但不允许修改该值。只读属性通常用于封装类的内部状态,确保数据的不可变性(immutability),从而提高代码的安全性和可维护性。
只读属性可以通过以下方式实现:
- 使用get访问器而不提供set访问器
- 使用init访问器(C# 9.0引入,允许对象初始化期间赋值)
- 使用readonly字段作为属性支持字段
基本语法[编辑 | 编辑源代码]
以下是只读属性的基本语法:
public class Person
{
// 只读属性(传统方式)
public string Name { get; } // 只能在构造函数或声明时初始化
// 使用init访问器(C# 9.0+)
public int Age { get; init; } // 可在构造函数或对象初始化器中赋值
public Person(string name, int age)
{
Name = name;
Age = age;
}
}
详细说明[编辑 | 编辑源代码]
传统只读属性[编辑 | 编辑源代码]
传统只读属性(仅有get访问器)的值只能在构造函数或声明时设置:
public class Circle
{
public double Radius { get; } // 只读属性
public Circle(double radius)
{
Radius = radius; // 只能在构造函数中赋值
}
public double Area => Math.PI * Radius * Radius; // 计算属性
}
使用init访问器[编辑 | 编辑源代码]
C# 9.0引入的init访问器提供了更灵活的只读属性初始化方式:
public class Product
{
public string Id { get; init; } // 可在对象初始化器中赋值
public string Name { get; init; }
// 使用示例:
// var p = new Product { Id = "P001", Name = "Laptop" };
// 之后不能再修改Id和Name
}
计算型只读属性[编辑 | 编辑源代码]
只读属性也可以是基于其他字段或属性计算得出的值:
public class Rectangle
{
public double Width { get; set; }
public double Height { get; set; }
// 计算型只读属性
public double Area => Width * Height;
// 使用示例:
// var rect = new Rectangle { Width = 10, Height = 5 };
// Console.WriteLine(rect.Area); // 输出50
}
实际应用场景[编辑 | 编辑源代码]
不可变对象[编辑 | 编辑源代码]
只读属性常用于创建不可变对象,这在多线程环境中特别有用:
public class ImmutablePoint
{
public int X { get; }
public int Y { get; }
public ImmutablePoint(int x, int y)
{
X = x;
Y = y;
}
// 方法返回新实例而不是修改现有实例
public ImmutablePoint Move(int dx, int dy) => new(X + dx, Y + dy);
}
配置对象[编辑 | 编辑源代码]
只读属性适合用于配置对象,确保配置一旦设置就不能被意外修改:
public class AppSettings
{
public string DbConnectionString { get; init; }
public int MaxRetryCount { get; init; }
public bool EnableLogging { get; init; }
// 使用示例:
// var settings = new AppSettings {
// DbConnectionString = "Server=...",
// MaxRetryCount = 3,
// EnableLogging = true
// };
}
性能考虑[编辑 | 编辑源代码]
只读属性通常不会引入显著的性能开销。对于简单的属性访问,JIT编译器通常会内联(inline)这些调用。然而,对于计算密集型属性,应考虑缓存结果:
public class ExpensiveCalculation
{
private double? _cachedResult;
private readonly double _input;
public double Result
{
get
{
if (_cachedResult == null)
{
// 模拟耗时计算
_cachedResult = Enumerable.Range(0, 1000000)
.Select(i => Math.Pow(_input, i) / Factorial(i))
.Sum();
}
return _cachedResult.Value;
}
}
private static double Factorial(int n) => n <= 1 ? 1 : n * Factorial(n - 1);
public ExpensiveCalculation(double input) => _input = input;
}
与只读字段的比较[编辑 | 编辑源代码]
只读属性与readonly字段有相似之处,但存在重要区别:
特性 | 只读属性 | readonly字段 |
---|---|---|
可以有各种访问修饰符 | 只能通过字段访问控制 | ||
可以包含计算逻辑 | 只能是存储的值 | ||
可以实现接口属性 | 不能直接实现接口成员 | ||
通常被序列化器支持 | 可能需要特殊处理 |
高级主题[编辑 | 编辑源代码]
只读属性的反射[编辑 | 编辑源代码]
通过反射可以绕过只读属性的限制,但应谨慎使用:
public class ReadOnlyDemo
{
public string Value { get; } = "Initial";
public static void ModifyReadOnlyProperty()
{
var obj = new ReadOnlyDemo();
Console.WriteLine(obj.Value); // 输出"Initial"
var prop = typeof(ReadOnlyDemo).GetProperty("Value");
prop.SetValue(obj, "Modified");
Console.WriteLine(obj.Value); // 输出"Modified"
}
}
只读接口属性[编辑 | 编辑源代码]
接口可以定义只读属性,实现类可以选择如何实现:
public interface IReadOnlyData
{
string Data { get; } // 只读接口属性
}
public class DataImplementation : IReadOnlyData
{
public string Data { get; } = "Fixed Data";
}
public class ComputedData : IReadOnlyData
{
public string Data => DateTime.Now.ToString(); // 每次访问计算新值
}
最佳实践[编辑 | 编辑源代码]
- 优先使用只读属性而不是公共字段
- 对于真正的不可变对象,使用只读属性和readonly字段的组合
- 考虑使用init访问器来提供更灵活的对象初始化方式
- 对于计算密集型属性,考虑缓存结果
- 避免通过反射修改只读属性,除非有充分理由
总结[编辑 | 编辑源代码]
C#只读属性是创建安全、不可变对象的重要工具。它们提供了一种封装数据的方式,同时确保数据不会被意外修改。从简单的值封装到复杂的计算属性,只读属性在C#编程中有广泛的应用。随着C#语言的演进(如init访问器的引入),只读属性的使用变得更加灵活和强大。