C#彻底搞懂属性(Property)与字段(Field)
一、字段(Field)
字段是类或结构中直接声明的变量。它的唯一使命,就是作为对象状态的一部分,存储数据。
1. 字段的声明
public class Student
{
// 这就是两个字段
public string Name; // 学生姓名
public int Age; // 学生年龄
}
2. 访问修饰符 -- Public
Student stu = new Student();
stu.Name = "张三";
stu.Age = -10; // 灾难!年龄怎么可能是负数?
Console.WriteLine($"{stu.Name}的年龄是 {stu.Age}岁。");
// 输出: 张三的年龄是 -10岁。
- 使用
Public可以在任何时候,任何地方直接访问,并且修改,这会存在某些隐患
3. 访问修饰符 -- Private
- Getter/Setter方法模式
public class Student
{
// 将字段设为private,外界无法直接访问
private string _name;
private int _age;
// 为了让外界能设置和获取值,我们提供一对公开的方法
public string GetName()
{
return _name;
}
public void SetName(string name)
{
// 可以在这里加入验证逻辑
if (string.IsNullOrWhiteSpace(name))
{
Console.WriteLine("姓名不能为空!");
return;
}
_name = name;
}
public int GetAge()
{
return _age;
}
public void SetAge(int age)
{
// 验证逻辑!
if (age < 0 || age > 150)
{
Console.WriteLine("年龄不合法!");
return;
}
_age = age;
}
}
// === 使用 ===
Student stu = new Student();
stu.SetName("李四");
stu.SetAge(-10); // 输出: 年龄不合法!
stu.SetAge(25);
Console.WriteLine($"{stu.GetName()}的年龄是 {stu.GetAge()}岁。");
二、属性(Property)
属性是字段的自然演进。它为私有字段提供了一个公开的、受控的访问层,但在语法上,它看起来就像一个公有字段。它巧妙地结合了方法的灵活性和字段的简洁性。
1. 属性的完整声明
一个完整的属性包含一个私有的字段和一对get/set访问器(Accessor)。
public class Student
{
// 1. 私有字段,用于实际存储数据
private int _age;
// 2. 公开属性,作为外界访问的入口
public int Age
{
// 3. get访问器:当读取属性时执行
get
{
// 可以加入逻辑,比如权限检查
return _age;
}
// 4. set访问器:当给属性赋值时执行
set
{
// 'value'是一个上下文关键字,代表赋过来的值
if (value < 0 || value > 150)
{
// 可以抛出异常,这是更推荐的做法
throw new ArgumentOutOfRangeException(nameof(Age), "年龄必须在0到150之间。");
}
_age = value;
}
}
}
// === 使用属性 ===
Student stu = new Student();
try
{
stu.Age = 25; // 调用set访问器,value为25
Console.WriteLine(stu.Age); // 调用get访问器
stu.Age = -10; // 调用set访问器,value为-10,抛出异常
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
三、属性的进阶 - 语法糖
C#语言在不断发展,属性的写法也越来越简洁。
1. 自动实现属性(Auto-Implemented Properties)
在很多情况下,我们的属性不需要任何特殊的验证逻辑,只是简单地存取一个值。为这种情况手写字段和get/set代码块显得很冗余。于是,C# 引入了自动属性。
public class Product
{
// 这就是自动属性
// 编译器会自动在幕后创建一个私有的、匿名的字段
public string Name { get; set; }
public decimal Price { get; set; }
}
// === 使用 ===
Product phone = new Product();
phone.Name = "Phone X"; // 背后调用了自动生成的set
phone.Price = 999.99m;
这是我们日常开发中最常用的形式。简洁、高效。只有当你需要添加自定义逻辑时,才需要退回到手动实现字段的方式。
2. 控制可访问性
我们可以为get和set访问器设置不同的访问级别。
public class Order
{
// Id只能在类的内部被设置(比如在构造函数中)
// 但可以在任何地方被读取
public int Id { get; private set; }
public DateTime OrderDate { get; } // 只有get,这是一个只读属性
public Order(int id)
{
this.Id = id; // 在内部设置是合法的
this.OrderDate = DateTime.UtcNow; // 只读属性只能在构造函数或声明时赋值
}
}
3. 表达式主体属性
对于一些只读的、由其他数据计算得出的属性,C# 提供了更简洁的写法。
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
// 这个只读属性的值是计算出来的
public string FullName => $"{FirstName} {LastName}";
}
四、C#中的特殊属性
1. init访问器
init 访问器让属性拥有了特殊的“只写一次”能力。它允许属性在对象初始化器中被赋值,但在那之后就变成只读。
public class Book
{
public string Isbn { get; init; } // 注意是init
public string Title { get; init; }
}
// === 使用 ===
var book = new Book
{
Isbn = "978-0321765723", // 合法!在对象初始化器中赋值
Title = "The C# Programming Language"
};
// book.Title = "New Title"; // 编译错误!初始化完成后,不能再修改
2. required修饰符
创建对象时必须为某个属性赋值。
public class User
{
public int Id { get; set; }
public required string Username { get; set; } // 必须在创建时提供值
public string? Email { get; set; } // 可选
}
// === 使用 ===
// var user1 = new User(); // 编译错误!Username是required的,但没有被赋值。
var user2 = new User { Username = "alice" }; // 正确
结语
点个赞,关注我获取更多实用 C# 技术干货!如果觉得有用,记得收藏本文!