第十五章、实现属性以访问字段

本文深入讲解C#中的属性概念,包括属性的定义、使用及限制条件,并介绍了如何在接口中声明属性,以及如何生成和使用自动属性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  属性既维持了封装性,又可以使用字段风格的语法。

  什么是属性

  属性是字段和方法的交集——看起来像字段,用起来像方法。访问属性所使用的语法和访问字段相同。然而,编译器会将这种风格的语法自动转换成对特定访问器方法的调用。属性的声明如下所示:

  访问修饰符 类型 属性名

  {

  get

  {

  //取值代码

  }

  set

  {

  //赋值代码

  }

  }

  属性可以包含两个代码块,分别以get和set关键字开头。其中,get块包含读取属性时执行的语句,set块包含向属性写入时执行的语句。属性的类型指定了由get和set访问器读取和写入的数据的类型。

  所有set访问器都用一个隐藏的、内建的参数(名为value)来传递要写入的数据。

  struct ScreenPosition{

  private int _x,_y;

  public ScreenPosition(int X,int Y)

  {

  this._x = rangeCheckedX(X);

  this._y = rangeCheckedY(Y);

  }

  public int X  //定义属性

  {

  get {return this._x;}

  set{this._x = rangeCheckedX(value);}

  }

  public int Y  //定义属性

  {

  get {return this._y;}

  set{this._y = rangeCheckedY(value);}

  }

  private static int rangeCheckedX(int x){.......}

  private static int rangeCheckedY(int y){.......}

  }

  属性唯一要求的就是由get访问器返回指定类型的值。值还能动态计算获得,不一定要从存储好的数据中获取。

  使用属性

  在表达式中使用属性时,要么取值,要么赋值。

  ScreenPosition origin = new  ScreenPosition(0,0);

  //取值,调用后get访问器

  int xpos = origin.X //实际调用origin.X.get

  int ypos = origin.Y //实际调用origin.Y.get

  //赋值,调用set访问器

  origin.X = 40;  //实际

       origin.X += 40; //同时取值和赋值,get和se调用origin.X.set , value = 40

  origin.Y = 100;  //实际调用origin.Y.set , value = 100t访问器都会被调用

  只读属性:可以声明只包含get访问器的属性,这称为只读属性。只读属性不包含set访问器,对其进行写入操作会报告编译错误。

  只写属性:类似的,可声明只包含set访问器的属性,这称为只写属性。只写属性不包含get访问器,对其进行读取操作会报告编译错误。只写属性适用于对密码这样的数据进行保护。

  属性在外观、行为和感觉上都像字段。但属性本质上是方法而不是字段。另外,属性存在以下限制:

  1、只有在结构或类初始化好之后,才能通过该结构或类的属性来赋值。

  ScreenPosition  location;

  location.X = 40;//编译错误,location尚未赋值

  2、不能将属性作为ref和out参数传给方法;但可写的字段能作为ref和out参数值传递。

  MyMethod(ref location.X);  //编译时错误

  3、属性中最多只能包含一个get和一个set访问器。不能包含其他方法、字段或属性。

  4、get和set访问器不能获取任何参数。要赋的值会通过内建的、隐藏的value变量自动传给set访问器。

  5、不能声明const属性。

  const int X{ get{...} set{...} }//编译错误

  在接口中声明属性

  除了可以在接口中定义方法之外,还可以定义属性。为此,需要指定get或set关键字,或者同时指定这两个关键字。但要将get或set访问器的主体替换成分号。如下:

  interface IScreenPosition

  {

  int X {get;set;} //没有修饰符

  int Y{get;set;} //没有修饰符

  }

  实现这个接口的任何类或结构都必须实现X和Y属性,并在属性中定义get和set访问器。如下:

  struct ScreenPosition: IScreenPosition

  {

  .............

  public int X

  {

  get{....}

  set{.....}

  }

  public int Y

  {

  get{....}

  set{.....}

  }

  ..............

  }

  在类中实现接口的属性时,可将属性的实现声明为virtual,从而允许派生类重写实现。如下:

  struct ScreenPosition: IScreenPosition

  {

  .............

  public virtual  int X

  {

  get{....}

  set{.....}

  }

  public virtual  int Y

  {

  get{....}

  set{.....}

  }

  ..............

  }

  还可使用显示接口实现语法来实现属性。属性的显示实现是非公共和非虚的(因而不能被重载)。

  生成自动属性

  C#编译器现在能自动为属性生成代码,如下:

  class Circle{

  public int Radius{ get; set; }

  ..........

  }

  在这个例子中,Circle类包含名为Radius的属性。除了属性的类型,不必指定这个属性是如何工作的——get和set访问器都是空白的。C#编译器自动将这个定义转换成私有字段以及一个默认的实现,如下所示:

  class Circle{

  private int _radius;

  public int Radius{

  get{ return this. _radius;}

  set{ this. _radius=value;}

  }

  .........

  }

  定义自动属性时,语法与在接口中定义属性几乎完全相同。区别在于,自动属性可以指定访问修饰符,例如private,public,或者protect。

  使用属性来初始化对象

  public class Triangle

  {

  private int side1Length = 10;

  private int side2Length = 10;

  private int side3Length = 10;

  public int side1Length

  {

  set{this. side1Length  = value;}

  }

  public int side2Length

  {

  set{this. side2Length  = value;}

  }

  public int side3Length

  {

  set{this. side3Length  = value;}

  }

  }

  创建类的实例时,可以提供了set访问器的任何公共属性指定名称和值。如下:

  Triangle tri1= new Triangle{side3Length  = 15;}

  Triangle tri2= new Triangle{side1Length  = 15;side3Length  = 20;}

  Triangle tri3= new Triangle{side2Length  = 15;side3Length  = 20;}

  Triangle tri4= new Triangle{side1Length  = 15;side2Length  = 15;side3Length  = 20;}

  这个语法称为对象初始化器。像这样调用对象初始化器,C#编译器会自动生成代码来调用默认构造器,然后调用每个具名属性的set访问器,把它初始化成指定值。还可将对象初始化器与非默认构造器配合使用。例如,假定Triangle类还有一个构造器能获取单个字符串参数(描述三角形的类型),那么可以调用这个构造器,同时对其他属性进行初始化;

  Triangle tri4= new Triangle("等边三角形"){side1Length  = 15;side2Length  = 15;side3Length  = 15;}

  对象初始化器与自动属性配合使用

  Polygon类

  using System;

  using System.Collections.Generic;

  using System.Linq;

  using System.Text;

  using System.Threading.Tasks;

  namespace AutomaticProperties

  {

  class Polygon

  {

  public int NumSides { get; set; }

  public double SideLength { get; set; }

  public Polygon()

  {

  this.NumSides = 4;

  this.SideLength = 10.0;

  }

  }

  }

  Program类

  using System;

  using System.Collections.Generic;

  using System.Text;

  namespace AutomaticProperties

  {

  class Program

  {

  static void doWork()

  {

  Polygon square = new Polygon();

  Polygon triangle = new Polygon { NumSides = 3 };

  Polygon pentagon = new Polygon { SideLength = 15.5, NumSides = 5 };

  Console.WriteLine("Square: number of sides is {0}, length of each side is {1}",

  square.NumSides, square.SideLength);

  Console.WriteLine("Triangle: number of sides is {0}, length of each side is {1}",

  triangle.NumSides, triangle.SideLength);

  Console.WriteLine("Pentagon: number of sides is {0}, length of each side is {1}",

  pentagon.NumSides, pentagon.SideLength);

  }

  static void Main(string[] args)

  {

  try

  {

  doWork();

  }

  catch (Exception ex)

  {

  Console.WriteLine(ex.Message);

  }

  }

  }

  }

  

 

转载于:https://www.cnblogs.com/linhuide/p/5819885.html

<think>我们有一个MutableList,需要按照多个字段排序:先按字段a排序,再按字段b排序。在Kotlin中,我们可以使用`sortedWith`或`sortWith`函数,并传入一个比较器(Comparator)。对于多字段排序,我们可以使用`compareBy`或`thenBy`来构建比较器。具体步骤:1.使用`compareBy`指定第一个排序字段字段a)。2.使用`thenBy`指定后续排序字段字段b)。3.将构建的比较器传递给`sortWith`函数(对原列表进行排序)或`sortedWith`函数(返回排序后的新列表)。注意:`sortWith`会直接修改原MutableList,而`sortedWith`会返回一个新的排序后的列表,原列表不变。假设列表中元素的类型为`Item`,有两个属性:`a`和`b`。示例代码:```kotlindataclassItem(vala:Int,valb:Int)funmain(){vallist=mutableListOf(Item(1,3),Item(1,2),Item(2,1),Item(2,0))//按原列表排序(修改原列表)list.sortWith(compareBy<Item>{it.a}.thenBy{it.b})//或者,使用sortedWith生成新列表(原列表不变)//valsortedList=list.sortedWith(compareBy<Item>{it.a}.thenBy{it.b})println(list)//输出:[Item(a=1,b=2),Item(a=1,b=3),Item(a=2,b=0),Item(a=2,b=1)]}```在上面的代码中:-`compareBy<Item>{it.a}`:首先按字段a(升序)排序。-`.thenBy{it.b}`:然后按字段b(升序)排序。如果需要降序排序,可以使用`compareByDescending`和`thenByDescending`,或者使用`-`符号反转(如果字段是数字类型)或者使用`thenComparator`自定义。例如,先按a升序,再按b降序:```kotlinlist.sortWith(compareBy<Item>{it.a}.thenByDescending{it.b})```或者,更通用的方式,我们可以使用`then`函数并传入一个比较器:```kotlinlist.sortWith(compareBy<Item>{it.a}.then(compareByDescending{it.b}))```以上就是在Kotlin中对MutableList进行多字段排序的方法。注意:如果排序字段可能为null,我们可以使用`compareBy`和`thenBy`的可空版本,它们会自动处理null值(将null放在最前或最后,取决于比较器)。默认情况下,升序排序时null会被放在最前面。如果需要调整,可以使用`nullsFirst`或`nullsLast`比较器。例如,字段a和b都是可空的Int(Int?),我们希望将null放在最后:```kotlinlist.sortWith(compareBy<Item>(nullsLast()){it.a}.thenBy(nullsLast()){it.b})```但注意,`compareBy`和`thenBy`本身并不直接支持传入`nullsLast`,所以我们需要使用`Comparator.nullsLast`来包装字段的比较器。另一种写法:```kotlinlist.sortWith(compareBy<Item>{it.a?:Int.MAX_VALUE}.thenBy{it.b?:Int.MAX_VALUE})//或者,更安全的方式是使用Comparator来处理nulllist.sortWith(Comparator{o1,o2->//自定义比较逻辑,处理null//这里略,可以使用compareValuesBy函数简化compareValuesBy(o1,o2,{it.a},//默认的null处理是null小于非null,所以升序时null在前,我们可以调整{it.b})//但compareValuesBy默认的null处理是null小于非null,所以如果我们想将null放在后面,需要自己写Comparator})```更简单的方式是使用Kotlin标准库中的`nullsLast`组合:```kotlinvalcomparator=compareBy<Item>{it.a?:Int.MIN_VALUE}//或者使用一个特殊值,但可能不通用//推荐:使用Comparator的扩展函数valcomparator=Comparator<Item>{o1,o2->//先比较a,处理nullvalaCompare=when{o1.a==null&&o2.a==null->0o1.a==null->1//将null视为更大(放后面)o2.a==null->-1else->o1.a.compareTo(o2.a)}if(aCompare!=0)aCompareelse{//再比较b,同样处理nullwhen{o1.b==null&&o2.b==null->0o1.b==null->1o2.b==null->-1else->o1.b.compareTo(o2.b)}}}```不过,Kotlin标准库提供了`compareBy`和`thenBy`,它们默认使用自然顺序,并且null会被视为小于任何非null值(即升序时null在前)。所以如果我们需要改变null的位置,可能需要自定义比较器。但通常,我们可以使用`compareValuesBy`函数并指定多个属性,然后自己处理null?实际上,`compareValuesBy`也是使用自然顺序,包括null的处理。因此,对于可空字段,我们可以这样:如果希望null排在最后(升序):```kotlinlist.sortWith(compareBy<Item>{it.a!=null}//先按是否为null排序,这样非null的排在前面(因为true>false,但我们要的是非null在前,所以可以取反?).thenBy{it.a}//然后对非null的部分按a排序.thenBy{it.b!=null}.thenBy{it.b})```但这样写比较繁琐。另一种方式是使用`Comparator.nullsLast`,但它是对整个对象进行null处理,而不是对字段。因此,我们可以为每个字段创建一个处理null的比较器,然后组合:```kotlinvalaComparator=compareBy<Item>{it.a}//默认null在前//自定义一个将null放在最后的比较器valaNullsLast=Comparator<Item>{o1,o2->if(o1.a==null&&o2.a==null)0elseif(o1.a==null)1elseif(o2.a==null)-1elseo1.a.compareTo(o2.a)}valbNullsLast=...//类似list.sortWith(aNullsLast.then(bNullsLast))```或者,使用Kotlin标准库中的扩展函数:```kotlinvalcomparator=compareBy<Item>(nullsLast(compareBy{it.a})){it}//但是这样不行,因为nullsLast是针对整个对象的//我们可以使用:fun<T>nullsLastComparator(comparator:Comparator<inT>):Comparator<T>=Comparator{a,b->if(a==null&&b==null)0elseif(a==null)1elseif(b==null)-1elsecomparator.compare(a,b)}//但这里是对整个对象T的可空性处理,不是对字段。//因此,针对字段,我们可以这样构建:valaComparator=Comparator<Item>{o1,o2->vala1=o1.avala2=o2.aif(a1==null&&a2==null)0elseif(a1==null)1elseif(a2==null)-1elsea1.compareTo(a2)}valcomparator=aComparator.thenCompare{o1,o2->valb1=o1.bvalb2=o2.bif(b1==null&&b2==null)0elseif(b1==null)1elseif(b2==null)-1elseb1.compareTo(b2)}```或者,更简洁地使用`compareValues`和`compareValuesBy`的变体,但Kotlin标准库没有直接提供,我们可以自己写一个工具函数。不过,由于问题中没有提到字段可空,我们假设字段不可空。所以最初给出的代码就是解决方案。总结:对于不可空字段,使用`compareBy`和`thenBy`即可。对于可空字段,需要自定义比较器来处理null值的位置。现在,我们回到用户的问题:用户没有提到可空性,所以我们提供基本的多字段排序方法。因此,回答如下:</think>在 Kotlin 中,对 `MutableList` 进行多字段排序(先按字段 `a` 再按字段 `b`)可以通过链式比较器实现,以下是具体实现方法: ### 核心实现代码 ```kotlin // 数据类示例 data class Item(val a: Int, val b: Int, val c: String) fun main() { val list = mutableListOf( Item(3, 10, "A"), Item(1, 20, "B"), Item(1, 15, "C"), Item(2, 5, "D") ) // 多字段排序:先按 a 升序,再按 b 升序 list.sortWith(compareBy<Item> { it.a }.thenBy { it.b }) // 输出结果 println(list) // 结果:[ // Item(a=1, b=15, c=C), // Item(a=1, b=20, c=B), // Item(a=2, b=5, c=D), // Item(a=3, b=10, c=A) // ] } ``` ### 关键语法解析 1. **链式比较器** ```kotlin list.sortWith(compareBy<Item> { it.a }.thenBy { it.b }) ``` - `compareBy { it.a }`:定义主排序字段字段 `a`) - `.thenBy { it.b }`:添加次级排序字段字段 `b`) 2. **升降序控制** - 升序(默认):`compareBy` / `thenBy` - 降序:`compareByDescending` / `thenByDescending` ```kotlin // 先按 a 降序,再按 b 升序 list.sortWith(compareByDescending<Item> { it.a }.thenBy { it.b }) ``` 3. **空安全处理** 若字段可能为 `null`,使用 `nullsFirst`/`nullsLast`: ```kotlin list.sortWith( compareBy<Item> { it.a ?: Int.MIN_VALUE } .thenBy(nullsLast()) { it.b } // null 值排在最后 ) ``` ### 扩展说明 1. **性能优化** Kotlin 的比较器链在底层通过 `Comparator` 接口实现,时间复杂度为 $O(n \log n)$,与标准排序算法一致[^1]。 2. **自定义比较逻辑** 如需复杂逻辑(如字符串特殊规则),可扩展比较器: ```kotlin list.sortWith(Comparator { o1, o2 -> val aCompare = o1.a.compareTo(o2.a) if (aCompare != 0) aCompare else o1.b.compareTo(o2.b) // a 相同时比较 b }) ``` 3. **不可变列表排序** 对 `List` 使用 `sortedWith` 返回新列表: ```kotlin val sorted = list.sortedWith(compareBy<Item> { it.a }.thenBy { it.b }) ``` ### 注意事项 - **属性访问**:若属性涉及复杂计算,建议提前缓存值避免重复计算 - **大列表优化**:超过 10,000 元素时考虑使用 `toMutableList().sort()` 减少中间对象[^4] - **稳定性**:Kotlin 的排序是稳定的,相等元素保持原始顺序 --- ### 相关问题 1. Kotlin 中如何对三字段(a→b→c)进行嵌套排序? 2. 当排序字段为自定义类时,如何实现 `Comparable` 接口进行多级排序? 3. 如何通过 Kotlin 扩展函数为 `MutableList` 添加链式排序的 DSL 语法? 4. 在 Android 开发中,对 `RecyclerView` 数据列表进行多字段排序的最佳实践是什么? [^1]: Kotlin 属性字段的默认实现会生成高效访问器,适合排序场景 [^4]: Kotlin 集合操作建议使用安全访问方法避免越界
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值