C# --- Constructor, Object Initializer, Property, Constant, and Readonly
Constructor and Object Initializer
- constructor用来初始化, 可以被overload, 通过input parameter识别调用哪个constructor
- 如果没有自定义的constructor, 编译器会自动生成一个无参constructor
- 如果有自定义的constructor, 则不会自动生成无参constructor. 如果需要无参constructor,需要显示的写出来
- constructor的权限一般都是public, 但是也可以用private修饰. 这样可以控制实例的生成. (如单例模式用static方法返回唯一的实例)
- 使用this关键字可以在一个constructor调用另外一个constructor
public class Wine
{
public decimal Price;
public int Year;
public Wine (decimal price) { Price = price; }
public Wine (decimal price, int year) : this (price) { Year = year; }
public Wine (decimal price, DateTime year) : this (price, year.Year) { }
}
Object Initializer
- Object Initializer提供了另外一种初始化Object的方法
public class Bunny
{
public string Name;
public bool LikesCarrots;
public bool LikesHumans;
public Bunny () {}
public Bunny (string n) { Name = n; }
}
// Note parameterless constructors can omit empty parentheses
Bunny b1 = new Bunny { Name="Bo", LikesCarrots=true, LikesHumans=false };
Bunny b2 = new Bunny ("Bo") { LikesCarrots=true, LikesHumans=false };
//The code to construct b1 and b2 is precisely equivalent to the following:
//The temporary variables are to ensure that if an exception is thrown during initiali‐
//zation, you can’t end up with a half-initialized object.
Bunny temp1 = new Bunny(); // temp1 is a compiler-generated name
temp1.Name = "Bo";
temp1.LikesCarrots = true;
temp1.LikesHumans = false;
Bunny b1 = temp1;
Bunny temp2 = new Bunny ("Bo");
temp2.LikesCarrots = true;
temp2.LikesHumans = false;
Bunny b2 = temp2;
为什么使用 Object Initializer 而不使用构造函数
- 通常来讲使用构造函数更好,这样可以防止构建的object存在invalid state的情况 (如在多线程的情况下)
- 但是有一些成员变量的初始化不是必要的, 而是optional的. 这是就需要将构造函数overload来满足不同的初始化需求. 而大量的构造函数重载降低了代码可读性并且增加了代码的复杂度. 这时候就可以使用Object Initializer.
- 可以用在创建Anonymous object:
var v = new { Amount = 108, Message = "Hello" };
并且用在LINQ中, 如下
var productInfos =
from p in products
select new { p.ProductName, p.UnitPrice };
MyObject myObjectInstance = new MyObject(param1, param2)
{
MyProperty = someUsefulValue
};
//This will behave about the same as if you do this:
MyObject myObjectInstance = new MyObject(param1, param2);
myObjectInstance.MyProperty = someUsefulValue;
Fields and Property
Fields
- 一个类或者struct的成员变量被称为 field
- 一个filed可以被readonly关键字修饰, readonly可以保证在变量在被创建完成后不会被修改.
- readonly变量只能在声明或者构造函数里被赋值
- 注意如果用readonly修饰一个collection, readonly只能保证collection的reference不会被修改, 但是collection里面的内容还是可以被修改的, 推荐使用Immutable library
- filed可以不用被初始化, 未被初始化的field有default value (0, ‘\0’, null, false)
- field的初始化在构造函数之前执行
Property
- Property 提供对一个private fields的读写操作. (也就是集成了setter 和 getter)
- 一个Property包括set 和 get
- get会返回对应private field的值
- set可以对传入的数值进行一些validation或者转换, 然后将值赋给对应的private field
Example:
- In this example, the TimePeriod class represents an interval of time.
- Internally, the class stores the time interval in seconds in a private field named
_seconds.
and a read-write property namedHours
allows the customer to specify the time interval in hours.- Both the get and the set accessors perform the necessary conversion between hours and seconds.
- In addition, the set accessor validates the data and throws an ArgumentOutOfRangeException if the number of hours is invalid.
public class TimePeriod
{
private double _seconds;
public double Hours
{
get { return _seconds / 3600; }
set
{
if (value < 0 || value > 24)
throw new ArgumentOutOfRangeException(nameof(value),
"The valid range is between 0 and 24.");
_seconds = value * 3600;
}
}
}
TimePeriod t = new TimePeriod();
// The property assignment causes the 'set' accessor to be called.
t.Hours = 24;
// Retrieving the property causes the 'get' accessor to be called.
Console.WriteLine($"Time in hours: {t.Hours}");
// The example displays the following output:
// Time in hours: 24
Expression body definitions
- 使用expression定义setter和getter
public class SaleItem
{
string _name;
decimal _cost;
public SaleItem(string name, decimal cost)
{
_name = name;
_cost = cost;
}
public string Name
{
get => _name;
set => _name = value; //value不是自定义的 代表输入的值
}
public decimal Price
{
get => _cost;
set => _cost = value;
}
}
public class SettingValue : DatabaseDocument
{
public Id Identifier { get: init; }
public scope scope { get; init; }
[BsonIgnore]
public JsonElement JsonValue
{
get => BsonValue .ToJsonElement(); //通过BsonValue拿到
init => BsonValue = value .ToBsonValue(); //设置BasonValue
}
[BsonELement("Value")]
public BsonValue BsonValue f get; init: }
}
Auto-implemented properties
//编译器可以自动生成对应的field
public class SaleItem
{
public string Name
{ get; set; }
public decimal Price
{ get; set; }
}
Property initializers
- 可以用 property initializer 对 auto properties 进行初始化
public decimal CurrentPrice { get; set; } = 123;
//初始化List
public class ConfiqurationModel
{
public string Code { get; init; }
public IReadOnlylist<SectionDefinitionModel> Sections { get; init; } = Array.Empty<SectionDefinitionmiodel>()
public IReadonlylist<SettingDefinitionModel> Settings { get; init; } = Array.Empty<SettingDefinitionMlodel>():
}
readonly property
public int Maximum { get; } = 999;
没有set 表示这是readonly propertypublic Id Identifier { get: init; }
init表示只能在初始化的时候赋值, 其他情况只能读取- 下面代码是通过构造函数初始化值, 初始化之后就只能读取数据
public SettingDefinitionModelWithcontext(SettingDefinitionodel setting, string namespace, string sectionCode) : base(setting)
{
Namespace = @namespace;
SectionCode = sectionCode:
}
public string Namespace { get; }
public string SectionCode { get;}
如何设置常量
constant
- 用constant修饰的变量, 编译器会在编译时静态的求值. 也就是直接将变量替换成值. constant可以是bool, char, string, 任何内置的数值类型或者枚举类型
public static double Circumference (double radius)
{
return 2 * System.Math.PI * radius;
}
//会被编译成
public static double Circumference (double radius)
{
return 6.2831853071795862 * radius;
}
static readonly
- static readonly也可以用来设置常量, 但是不同的是, static readonly变量不会在编译的时候被替换, 所以遇到每次运行都需要新的值的情况,需要用static readonly修饰
static readonly DateTime StartupTime = DateTime.Now;
- 另外一个区别是,
public const decimal ProgramVersion = 2.3;
如果assembly Y引用了assembly X的常量. 当Y编译时, 会储存常量2.3. 但是如果X重新编译将值改为2.4. 则Y不会更新这个值,直到Y重新编译. 而static readonly会避免这个错误
- 其实就是const只有在重新编译的时候才会更新,但是static readonly会在运行时更新