MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

C#属性(Property)与索引器高级用法

2022-03-151.1k 阅读

C# 属性(Property)高级用法

自动实现属性的高级特性

在 C# 中,自动实现属性极大地简化了属性的声明。例如,我们有一个简单的 Person 类:

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

这里的 NameAge 就是自动实现属性。它们自动生成了一个隐藏的后台字段来存储值。

只读自动实现属性

有时候,我们希望属性在初始化后就不能再修改。这可以通过只读自动实现属性来实现。

public class Employee
{
    public string EmployeeId { get; }
    public string Name { get; set; }

    public Employee(string id, string name)
    {
        EmployeeId = id;
        Name = name;
    }
}

在上述 Employee 类中,EmployeeId 是只读属性。一旦在构造函数中赋值,就无法在类的外部修改。这在表示一些不可变的标识符时非常有用,比如员工编号、身份证号等。

只写自动实现属性

虽然不常见,但 C# 也支持只写自动实现属性。

public class SecretData
{
    private string _secret;
    public string Secret { set { _secret = value; } }

    public void PrintSecret()
    {
        Console.WriteLine($"The secret is: {_secret}");
    }
}

SecretData 类中,Secret 属性只有 set 访问器。这意味着只能在类的外部设置该属性的值,而不能读取。这种属性通常用于需要隐藏数据,只允许在内部逻辑中使用的场景。

完整属性的高级用法

完整属性允许我们对属性的 getset 访问器进行更精细的控制。

复杂的 get 逻辑

public class Circle
{
    private double _radius;

    public double Radius
    {
        get { return _radius; }
        set
        {
            if (value >= 0)
            {
                _radius = value;
            }
            else
            {
                throw new ArgumentException("Radius cannot be negative.");
            }
        }
    }

    public double Area
    {
        get { return Math.PI * Math.Pow(Radius, 2); }
    }
}

Circle 类中,Area 属性的 get 访问器依赖于 Radius 属性。它计算并返回圆的面积。这种复杂的 get 逻辑可以根据对象的其他状态动态计算值。

延迟加载

延迟加载是指在属性第一次被访问时才进行初始化。

public class LazyLoadedData
{
    private string _data;

    public string Data
    {
        get
        {
            if (_data == null)
            {
                // 模拟从数据库或文件中加载数据
                _data = "Loaded data from external source";
            }
            return _data;
        }
    }
}

LazyLoadedData 类中,Data 属性的 get 访问器检查 _data 是否为 null。如果是,则加载数据。这样可以避免在对象创建时就加载可能不需要的数据,提高性能。

依赖注入

属性可以用于依赖注入。假设我们有一个 Logger 接口和一个使用它的 UserService 类。

public interface ILogger
{
    void Log(string message);
}

public class ConsoleLogger : ILogger
{
    public void Log(string message)
    {
        Console.WriteLine($"Log: {message}");
    }
}

public class UserService
{
    private ILogger _logger;

    public ILogger Logger
    {
        get { return _logger; }
        set { _logger = value; }
    }

    public void RegisterUser(string username)
    {
        if (Logger != null)
        {
            Logger.Log($"User {username} is registering.");
        }
        // 实际的用户注册逻辑
    }
}

UserService 类中,Logger 属性用于注入一个 ILogger 的实例。这样,UserService 就可以灵活地使用不同的日志记录实现,而不需要在类内部硬编码日志记录逻辑。

属性的特性(Attribute)

特性是一种向程序的程序集、类型、方法、属性等添加元数据的方式。

[DisplayName] 特性

在 Windows Forms 或 WPF 应用中,DisplayName 特性可以用于为属性指定一个更友好的显示名称。

using System.ComponentModel;

public class Product
{
    [DisplayName("Product ID")]
    public int Id { get; set; }

    [DisplayName("Product Name")]
    public string Name { get; set; }
}

当在数据绑定或显示属性值时,DisplayName 特性指定的名称会被使用,而不是属性的实际名称。这对于用户界面的友好性非常有帮助。

[JsonProperty] 特性

在使用 JSON 序列化库(如 Newtonsoft.Json)时,JsonProperty 特性可以控制属性在 JSON 序列化和反序列化时的行为。

using Newtonsoft.Json;

public class Order
{
    [JsonProperty("order_id")]
    public int OrderId { get; set; }

    [JsonProperty("customer_name")]
    public string CustomerName { get; set; }
}

在上述 Order 类中,JsonProperty 特性指定了属性在 JSON 中的名称。这样,在序列化和反序列化 JSON 数据时,就会使用指定的名称,而不是 C# 属性的名称。

[XmlElement] 特性

在处理 XML 序列化时,XmlElement 特性非常有用。

using System.Xml.Serialization;

public class Book
{
    [XmlElement("book_title")]
    public string Title { get; set; }

    [XmlElement("book_author")]
    public string Author { get; set; }
}

Book 类中,XmlElement 特性指定了 XML 元素的名称。当对 Book 对象进行 XML 序列化时,会使用指定的元素名称。

静态属性

静态属性属于类本身,而不是类的实例。

public class MathUtils
{
    private static double _pi = 3.14159;

    public static double Pi
    {
        get { return _pi; }
    }
}

MathUtils 类中,Pi 是一个静态属性。它可以通过类名直接访问,而不需要创建类的实例。

double value = MathUtils.Pi;

静态属性通常用于表示一些与类相关的常量或共享数据。

继承中的属性

当一个类继承自另一个类时,它可以继承父类的属性,并根据需要进行重写或隐藏。

属性重写

属性重写允许子类提供父类属性的不同实现。

public class Shape
{
    public virtual double Area { get; }
}

public class Circle : Shape
{
    private double _radius;

    public Circle(double radius)
    {
        _radius = radius;
    }

    public override double Area
    {
        get { return Math.PI * Math.Pow(_radius, 2); }
    }
}

public class Rectangle : Shape
{
    private double _width;
    private double _height;

    public Rectangle(double width, double height)
    {
        _width = width;
        _height = height;
    }

    public override double Area
    {
        get { return _width * _height; }
    }
}

在上述代码中,Shape 类有一个虚拟的 Area 属性。CircleRectangle 类继承自 Shape 并分别重写了 Area 属性,以提供各自形状面积的计算逻辑。

属性隐藏

属性隐藏允许子类提供一个与父类同名但不同实现的属性。

public class BaseClass
{
    public virtual string Message { get { return "Base class message"; } }
}

public class DerivedClass : BaseClass
{
    public new string Message { get { return "Derived class message"; } }
}

DerivedClass 中,使用 new 关键字隐藏了 BaseClass 中的 Message 属性。当通过 DerivedClass 的实例访问 Message 属性时,会得到 DerivedClass 中定义的消息。但如果将 DerivedClass 的实例转换为 BaseClass,则会访问到 BaseClass 中的 Message 属性。

C# 索引器高级用法

索引器的基本概念回顾

索引器允许对象像数组一样通过索引进行访问。例如,我们有一个简单的 MyList 类,它模拟一个简单的列表。

public class MyList
{
    private int[] _items = new int[10];

    public int this[int index]
    {
        get
        {
            if (index >= 0 && index < _items.Length)
            {
                return _items[index];
            }
            else
            {
                throw new IndexOutOfRangeException();
            }
        }
        set
        {
            if (index >= 0 && index < _items.Length)
            {
                _items[index] = value;
            }
            else
            {
                throw new IndexOutOfRangeException();
            }
        }
    }
}

MyList 类中,this[int index] 就是索引器。它允许我们像访问数组一样访问 MyList 对象的元素。

MyList list = new MyList();
list[0] = 10;
int value = list[0];

多参数索引器

索引器并不局限于单个参数。我们可以定义具有多个参数的索引器。

public class Matrix
{
    private int[,] _matrix;

    public Matrix(int rows, int cols)
    {
        _matrix = new int[rows, cols];
    }

    public int this[int row, int col]
    {
        get
        {
            if (row >= 0 && row < _matrix.GetLength(0) && col >= 0 && col < _matrix.GetLength(1))
            {
                return _matrix[row, col];
            }
            else
            {
                throw new IndexOutOfRangeException();
            }
        }
        set
        {
            if (row >= 0 && row < _matrix.GetLength(0) && col >= 0 && col < _matrix.GetLength(1))
            {
                _matrix[row, col] = value;
            }
            else
            {
                throw new IndexOutOfRangeException();
            }
        }
    }
}

Matrix 类中,索引器有两个参数 rowcol,用于访问二维矩阵中的元素。

Matrix matrix = new Matrix(3, 3);
matrix[0, 0] = 1;
int value = matrix[0, 0];

泛型索引器

索引器也可以是泛型的。这在处理不同类型的数据集合时非常有用。

public class GenericList<T>
{
    private T[] _items = new T[10];

    public T this[int index]
    {
        get
        {
            if (index >= 0 && index < _items.Length)
            {
                return _items[index];
            }
            else
            {
                throw new IndexOutOfRangeException();
            }
        }
        set
        {
            if (index >= 0 && index < _items.Length)
            {
                _items[index] = value;
            }
            else
            {
                throw new IndexOutOfRangeException();
            }
        }
    }
}

GenericList<T> 类中,索引器根据泛型类型 T 来处理不同类型的数据。

GenericList<string> stringList = new GenericList<string>();
stringList[0] = "Hello";
string value = stringList[0];

索引器与接口

接口可以定义索引器,实现该接口的类必须提供索引器的实现。

public interface IIndexedCollection
{
    object this[int index] { get; set; }
}

public class MyCollection : IIndexedCollection
{
    private object[] _items = new object[10];

    public object this[int index]
    {
        get
        {
            if (index >= 0 && index < _items.Length)
            {
                return _items[index];
            }
            else
            {
                throw new IndexOutOfRangeException();
            }
        }
        set
        {
            if (index >= 0 && index < _items.Length)
            {
                _items[index] = value;
            }
            else
            {
                throw new IndexOutOfRangeException();
            }
        }
    }
}

在上述代码中,IIndexedCollection 接口定义了一个索引器。MyCollection 类实现了该接口,并提供了索引器的具体实现。

索引器的重载

一个类可以有多个索引器,只要它们的参数列表不同。这就是索引器的重载。

public class MixedCollection
{
    private string[] _strings = new string[10];
    private int[] _ints = new int[10];

    public string this[int index]
    {
        get
        {
            if (index >= 0 && index < _strings.Length)
            {
                return _strings[index];
            }
            else
            {
                throw new IndexOutOfRangeException();
            }
        }
        set
        {
            if (index >= 0 && index < _strings.Length)
            {
                _strings[index] = value;
            }
            else
            {
                throw new IndexOutOfRangeException();
            }
        }
    }

    public int this[string key]
    {
        get
        {
            int index = Array.IndexOf(_strings, key);
            if (index != -1 && index < _ints.Length)
            {
                return _ints[index];
            }
            else
            {
                throw new KeyNotFoundException();
            }
        }
        set
        {
            int index = Array.IndexOf(_strings, key);
            if (index != -1 && index < _ints.Length)
            {
                _ints[index] = value;
            }
            else
            {
                throw new KeyNotFoundException();
            }
        }
    }
}

MixedCollection 类中,有两个索引器。一个通过整数索引访问字符串数组,另一个通过字符串键访问整数数组。

MixedCollection collection = new MixedCollection();
collection[0] = "Item1";
collection["Item1"] = 10;
int value = collection["Item1"];

索引器的特性(Attribute)

和属性一样,索引器也可以应用特性。

using System.ComponentModel;

public class IndexedData
{
    private string[] _data = new string[10];

    [DisplayName("Indexed Element")]
    public string this[int index]
    {
        get
        {
            if (index >= 0 && index < _data.Length)
            {
                return _data[index];
            }
            else
            {
                throw new IndexOutOfRangeException();
            }
        }
        set
        {
            if (index >= 0 && index < _data.Length)
            {
                _data[index] = value;
            }
            else
            {
                throw new IndexOutOfRangeException();
            }
        }
    }
}

IndexedData 类中,DisplayName 特性应用于索引器。这在某些数据绑定或显示场景中可以为索引器指定一个更友好的显示名称。

索引器在集合类中的应用

在自定义集合类中,索引器是非常重要的。例如,我们可以实现一个简单的字典类。

public class MyDictionary<TKey, TValue>
{
    private List<TKey> _keys = new List<TKey>();
    private List<TValue> _values = new List<TValue>();

    public TValue this[TKey key]
    {
        get
        {
            int index = _keys.IndexOf(key);
            if (index != -1)
            {
                return _values[index];
            }
            else
            {
                throw new KeyNotFoundException();
            }
        }
        set
        {
            int index = _keys.IndexOf(key);
            if (index != -1)
            {
                _values[index] = value;
            }
            else
            {
                _keys.Add(key);
                _values.Add(value);
            }
        }
    }
}

MyDictionary<TKey, TValue> 类中,索引器通过键来访问和设置值。这模仿了标准字典类的行为,展示了索引器在集合类中的重要作用。

MyDictionary<string, int> dictionary = new MyDictionary<string, int>();
dictionary["Key1"] = 10;
int value = dictionary["Key1"];

索引器与迭代器

索引器和迭代器可以协同工作。例如,我们可以为自定义集合类实现一个迭代器,并结合索引器来提供更灵活的遍历方式。

public class MyEnumerableList<T>
{
    private List<T> _items = new List<T>();

    public T this[int index]
    {
        get
        {
            if (index >= 0 && index < _items.Count)
            {
                return _items[index];
            }
            else
            {
                throw new IndexOutOfRangeException();
            }
        }
        set
        {
            if (index >= 0 && index < _items.Count)
            {
                _items[index] = value;
            }
            else
            {
                throw new IndexOutOfRangeException();
            }
        }
    }

    public IEnumerator<T> GetEnumerator()
    {
        for (int i = 0; i < _items.Count; i++)
        {
            yield return this[i];
        }
    }
}

MyEnumerableList<T> 类中,索引器用于访问元素,而 GetEnumerator 方法实现了迭代器。这样,我们既可以通过索引器随机访问元素,也可以通过迭代器顺序遍历元素。

MyEnumerableList<int> list = new MyEnumerableList<int>();
list[0] = 1;
list[1] = 2;

foreach (int item in list)
{
    Console.WriteLine(item);
}

通过以上对 C# 属性和索引器高级用法的介绍,我们可以看到它们在构建复杂、灵活且高效的 C# 程序中起着至关重要的作用。无论是在企业级应用开发,还是在小型项目中,合理运用这些高级特性都能提升代码的质量和可维护性。