c#特性类_C++程序设计:原理与实践(基础篇)

(2) 2024-08-27 17:12

Hi,大家好,我是编程小6,很荣幸遇见你,我把这些年在开发过程中遇到的问题或想法写出来,今天说一说
c#特性类_C++程序设计:原理与实践(基础篇),希望能够帮助你!!!。

17.1 可空类型

值类型能不能是可空类型呢?可空类型也是值类型,但它是包含null值的值类型。你可以像下面这样表示可空类型:

int? nullable = null; 

int?就是可空的int类型,对于编译器而言,int?会被编译为Nullable<int>和Nullable,下面的代码演示了可空类型的使用方法:

// #region << 版 本 注 释 >> // /*---------------------------------------------------------------- // // Copyright (C) 2019 极客部落 // // 版权所有。 // // // // 文件名:Program.cs // // 文件功能描述: // // // // // // 创建者:GeekTribe // // 时间:14:05 // //----------------------------------------------------------------*/ // #endregion using System; ​ namespace MSN { class MainClass { private static void Display(int? nullable){ //HasValue指示可空对象是否有值 Console.WriteLine ("可空类型是否有值:{0}",nullable.HasValue); if (nullable.HasValue) { Console.WriteLine ("可空类型值:{0}",nullable.Value); } //GetValueOrDefault,代表如果可空类型对象有值,就用它的值返回;如果不包含,返回默认值0 ​ Console.WriteLine ("GetValueOrDefault:{0}",nullable.GetValueOrDefault()); Console.WriteLine ("GetValueOrDefault:{0}",nullable.GetValueOrDefault(2)); ​ //GetHashCode()代表Hasvalue为true,则为Value属性返回对象的哈西代码,如果为false,则为0 Console.WriteLine ("GetHashCode:{0}",nullable.GetHashCode()); } ​ public static void Main(string[] args){ int? value = 1; Console.WriteLine("可空类型有值输出的情况;"); ​ Display(value); ​ value = new Nullable<int>(); Display(value); } } } 

代码通过HasValue属性来判断可空类型是否有值,如果有值则直接输出可空类型的值;如果没有值,则通过GetValueOrDefault方法返回默认的值。

17.2 空合并操作符

空合并即【??】操作符,他会对左右两个操作数进行判断:如果左边的数不为null,则返回左边的数;如果左边的为null,就返回右边的数。这个操作符可以用于可空类型,也可以用于引用类型,但是不能用于值类型。因为??会将左边的数与null进行比较。值类型是不能与null比较的。下面代码演示用法:

// #region << 版 本 注 释 >> // /*---------------------------------------------------------------- // // Copyright (C) 2019 极客部落 // // 版权所有。 // // // // 文件名:Program.cs // // 文件功能描述: // // // // // // 创建者:GeekTribe // // 时间:14:05 // //----------------------------------------------------------------*/ // #endregion using System; ​ namespace MSN { class MainClass { private static void NullOprerator() { int? nullable = null; int? nullhasvalue = 1; int xx = nullable ?? 12; int y = nullhasvalue ?? 123; Console.WriteLine("无值的{0},有值的{1}", xx, y); ​ }• public static void Main(string[] args){ NullOprerator(); } } } 

17.3 匿名方法

匿名方法就是没有名字的方法,没有名字,所以只能在定义的时候进行调用,其他时候无法调用。

// #region << 版 本 注 释 >> // /*---------------------------------------------------------------- // // Copyright (C) 2019 极客部落 // // 版权所有。 // // // // 文件名:Program.cs // // 文件功能描述: // // // // // // 创建者:GeekTribe // // 时间:14:05 // //----------------------------------------------------------------*/ // #endregion using System; ​ namespace MSN { class MainClass { delegate void VoteDelegate(string name); public static void Main(string[] args){ VoteDelegate votedelegate = delegate (string nickename) { Console.WriteLine("{0}来投票了", nickename); }; ​ votedelegate("SomeBody"); } } } 

以上代码的好处在于,若使用了匿名方法,则不需要单独定义一个vote方法了,减少了代码行数,更有利于程序阅读。

匿名函数会形成闭包,当一个函数包含对另一个函数的调用时,或者内部的函数使用了外部函数的变量时,都会形成闭包。闭包可能会延长外部变量的生命周期。

下面代码分析变量的捕获过程:

// #region << 版 本 注 释 >> // /*---------------------------------------------------------------- // // Copyright (C) 2019 极客部落 // // 版权所有。 // // // // 文件名:Program.cs // // 文件功能描述: // // // // // // 创建者:GeekTribe // // 时间:14:05 // //----------------------------------------------------------------*/ // #endregion using System; ​ namespace MSN { class MainClass { delegate void ClosureDelegate(); private static void closureMethod(){ string outVariable = "外部变量"; string captureVariable = "被捕获的外部变量"; ​ ClosureDelegate closuredelegate = delegate { string localvariable = "匿名方法中的变量"; Console.WriteLine(captureVariable + " " + localvariable); }; ​ closuredelegate(); } ​ public static void Main(string[] args){ closureMethod(); } } } ​ 

被匿名方法捕获后,变量的生命周期会延长,只要还有任何委托实例引用它,它就一直存在,就不会在委托实例调用结束之后被垃圾回收释放掉。下面的例子演示了这个过程:

// #region << 版 本 注 释 >> // /*---------------------------------------------------------------- // // Copyright (C) 2019 极客部落 // // 版权所有。 // // // // 文件名:Program.cs // // 文件功能描述: // // // // // // 创建者:GeekTribe // // 时间:14:05 // //----------------------------------------------------------------*/ // #endregion using System; ​ namespace MSN { class MainClass { delegate void ClosureDelegate(); ​ private static ClosureDelegate CreateInstance() { int count = 1; ClosureDelegate closureDelegate = delegate { Console.WriteLine(count); count++; }; ​ closureDelegate(); return closureDelegate; } ​ public static void Main(string[] args){ ClosureDelegate test = CreateInstance(); test(); } } } 

17.4 迭代器

我们前面使用foreach进行遍历的时候,实际上使用的就是迭代器。C#2.0中,迭代器的实现如下面代码所示:

// #region << 版 本 注 释 >> // /*---------------------------------------------------------------- // // Copyright (C) 2019 极客部落 // // 版权所有。 // // // // 文件名:Program.cs // // 文件功能描述: // // // // // // 创建者:GeekTribe // // 时间:14:05 // //----------------------------------------------------------------*/ // #endregion using System; using System.Collections; ​ namespace MSN { public class Friend { private string name; public string Name { get { return name; } set { name = value; } } ​ public Friend(string name) { this.name = name; } } ​ public class Friends : IEnumerable { private Friend[] friendarray; public Friends() { friendarray = new Friend[] { new Friend("zhangsan"), new Friend("lisi"), new Friend("wangwu") ​ }; } ​ public Friend this[int index] { get { return friendarray[index]; } } ​ public int Count { get { return friendarray.Length; } } ​ public IEnumerator GetEnumerator() { for (int index = 0; index < friendarray.Length; index++) { ​ yield return friendarray[index]; } } } class MainClass { public static void Main(string[] args){ Friends collects = new Friends(); ​ foreach (Friend f in collects) { Console.WriteLine(f.Name); } } } } ​ 

这段代码使用了一个yield return就完成了迭代器的实现。它就是告诉编译器GetEnumerator方法不是一个普通的方法,而是实现迭代器的方法。当编译器看到这句代码时候,会在中间代码生成IEnumerator对象。

迭代器的执行过程: 开始foreach -> 调用GetEnumerator方法获得迭代器(friendCollection) -> 调用IEnumeratorMoveNext(in) -> 访问IEnumeratorCuurrent(Friend f),效率比较高。

17.5 自动实现属性

C#3.0之后,我们可以直接使用简化的属性来进行变量的get和set。

// #region << 版 本 注 释 >> // /*---------------------------------------------------------------- // // Copyright (C) 2019 极客部落 // // 版权所有。 // // // // 文件名:Program.cs // // 文件功能描述: // // // // // // 创建者:GeekTribe // // 时间:14:05 // //----------------------------------------------------------------*/ // #endregion using System; ​ namespace MSN { class Person { public string Name { get; set; } public int Age { get; set; } } class MainClass { public static void Main(string[] args){ Person p = new Person(); p.Age = 30; p.Name = "zhangsan"; ​ Console.WriteLine("name={0}, age={1}", p.Name, p.Age); } } } ​ 

C#结构体自动实现属性

public struct TestPerson{ public string Name{ get; set;} public TestPerson(string n):this(){ this.Name="gg"; } } 

结构体中,当我们使用自动实现的属性时候,必须显式的调用无参构造函数this(),否则会出现编译错误。当使用之前的那种属性的时候,可以不使用this,但是必须初始化所有的成员变量。

17.6 隐式类型

C#是强制类型语言,定义一个变量时候,需要声明变量的类型。C#3.0引入了隐式类型,可以使用关键字var来声明变量和数组,var关键字告诉编译器去根据变量的值来推断其类型。

使用隐式类型需要注意的地方如下:

1: 只能是局部变量,不能为字段(静态和实例都不可以)

2:声明时候必须初始化

3:变量不能初始化为一个方法组,也不能为一个匿名函数

4:变量不能初始化为null,因为null可以隐式转换为任何引用类型或者可空类型,无法推断

5:不能使用var声明方法中的参数类型。

eg:

// #region << 版 本 注 释 >> // /*---------------------------------------------------------------- // // Copyright (C) 2019 极客部落 // // 版权所有。 // // // // 文件名:Program.cs // // 文件功能描述: // // // // // // 创建者:GeekTribe // // 时间:14:05 // //----------------------------------------------------------------*/ // #endregion using System; ​ namespace MSN { class MainClass { public static void Main(string[] args){ var str = "One World, One Dream!"; str = 123;//❌:无法将类型'int'隐式转换为'string' ​ var intarray = new[] { 1, 2, 3, 4 }; var stringarray = new[] {"a", "b"}; var errorarray = new[] {"hello", 3 };//❌: 找不到隐式类型数组的最佳类型 } } } 

隐式类型的优点在于,好多名称比较长的类,两边都声明比较麻烦,使用var就简单了。但是有的时候需要查看某些数据的类型,就比较麻烦了。

17.7 对象集合初始化器

之前,我们调用构造函数的时候,有可能需要多个参数,那么我们就需要定义多个构造函数,比较麻烦。引入对象初始化器之后,我们就不需要定义多个构造函数了。使用方式如下形式:

// #region << 版 本 注 释 >> // /*---------------------------------------------------------------- // // Copyright (C) 2019 极客部落 // // 版权所有。 // // // // 文件名:Program.cs // // 文件功能描述: // // // // // // 创建者:GeekTribe // // 时间:14:05 // //----------------------------------------------------------------*/ // #endregion using System; ​ namespace MSN { public class Person { public string Name { get; set; } public int Age { set; get; } public int Weight { set; get; } public int Height { set; get; } ​ public Person(string name) { this.Name = name; } ​ //自定义的有参构造函数覆盖了默认的无参构造函数 //注释以下代码会出现编译错误 public Person() { }• } ​ class MainClass { public static void Main(string[] args){ //使用对象初始化器初始化器, 圆括号可以省略 Person p = new Person(){ Name = "Learning Hard", Age = 25, Weight = 60, Height = 70 }; ​ Console.WriteLine(p.Name + p.Age); } } } 

反编译代码可知,首先C#编译器生成了一个Person类的临时对象,并且调用Person类的默认无参构造函数进行初始化,然后对他的属性进行逐个赋值,并最终赋给对象p。

由此可以想到,要使用对象初始化器,你必须确保类具有一个无参构造函数。如果你自定义了一个有参构造函数而把默认的无参构造函数覆盖,你需要重新定义一个无参构造函数,否则会出现编译器错误。

【对象集合初始化器其他用途】

eg:

List<string> newnames = new List<string>{ "learning hard1","hard2","hard3" }; 

17.8 匿名类型

匿名类型,就是没有指明类型的类型,通过隐式类型和对象初始化器两种特性创建了一个未知类型的对象。减少了类定义过程的代码,减少了开发人员的工作量。

// #region << 版 本 注 释 >> // /*---------------------------------------------------------------- // // Copyright (C) 2019 极客部落 // // 版权所有。 // // // // 文件名:Program.cs // // 文件功能描述: // // // // // // 创建者:GeekTribe // // 时间:14:05 // //----------------------------------------------------------------*/ // #endregion using System; ​ namespace MSN { class MainClass { public static void Main(string[] args){ //定义匿名类型对象 var person = new {Name = "Zhangsan",Age = 25} ; Console.WriteLine ("{0}的年龄为{1}",person.Name,person.Age); //定义匿名类型数组 var personCollection = new[]{ new {Name="Tom",Age=30} , new {Name="Lily",Age=22} , new {Name="Jerry",Age=32} , } ; int totalAge = 0; foreach (var p in personCollection) { totalAge += p.Age; } Console.WriteLine ("所有人的年龄总和:{0}",totalAge); } } } ​ 

17.9 Lambda表达式

Lambda表达式可以理解为一个匿名方法,它可以包含表达式和语句,并且用于创建委托或转换为表达式树。在使用Lambda时候,都会使用【=>】运算符,该运算符的左边是匿名方法的输入参数,右边则是表达式或语句块。

Lambda 表达式是一种可用于创建委托或表达式目录树类型的匿名函数。通过使用 lambda 表达式,可以写入可作为参数传递或作为函数调用值返回的本地函数。Lambda 表达式对于编写 LINQ 查询表达式特别有用。

若要创建 Lambda 表达式,需要在 Lambda 运算符 => 左侧指定输入参数(如果有),然后在另一侧输入表达式或语句块。例如,lambda 表达式 x => x * x 指定名为 x 的参数并返回 x 的平方值。如下面的示例所示,你可以将此表达式分配给委托类型:

// #region << 版 本 注 释 >> // /*---------------------------------------------------------------- // // Copyright (C) 2019 极客部落 // // 版权所有。 // // // // 文件名:Program.cs // // 文件功能描述: // // // // // // 创建者:GeekTribe // // 时间:14:05 // //----------------------------------------------------------------*/ // #endregion using System; ​ namespace MSN { delegate int del(int i); class MainClass { public static void Main(string[] args){ del myDelegate = x => x * x; ​ int j = myDelegate(5); //j = 25 Console.WriteLine ("value = {0}",j); } } } 

Lambda分为两种,表达式lambda和语句lambda,下面依次介绍这两种之间的区别:

(1)表达式 lambda

表达式位于 => 运算符右侧的 lambda 表达式称为“表达式 lambda”。表达式 lambda 广泛用于表达式树(C# 和 Visual Basic)的构造。表达式 lambda 会返回表达式的结果,并采用以下基本形式。

 (input parameters) => expression

仅当 lambda 只有一个输入参数时,括号才是可选的;否则括号是必需的。括号内的两个或更多输入参数使用逗号加以分隔:

 (x, y) => x == y 

有时,编译器难以或无法推断输入类型。如果出现这种情况,你可以按以下示例中所示方式显式指定类型:

 (int x, string s) => s.Length > x 

使用空括号指定零个输入参数:

 () => SomeMethod() 

(2)语句 lambda

语句 lambda 与表达式 lambda类似,只是语句括在大括号中:

(input parameters) => {statement;} 

语句 lambda 的主体可以包含任意数量的语句。但是,实际上通常不会多于两个或三个。

// #region << 版 本 注 释 >> // /*---------------------------------------------------------------- // // Copyright (C) 2019 极客部落 // // 版权所有。 // // // // 文件名:Program.cs // // 文件功能描述: // // // // // // 创建者:GeekTribe // // 时间:14:05 // //----------------------------------------------------------------*/ // #endregion using System; ​ namespace MSN { delegate void TestDelegate(string s); class MainClass { public static void Main(string[] args) { TestDelegate myDel = str => { string s = str + " " + "World"; Console.WriteLine(s); }; ​ myDel("Hello"); } } } 

(3)Lambda 表达式中的变量范围

在定义 lambda 函数的方法内或包含 lambda 表达式的类型内,lambda 可以引用范围内的外部变量。以这种方式捕获的变量将进行存储以备在 lambda 表达式中使用,即使在其他情况下,这些变量将超出范围并进行垃圾回收。必须明确地分配外部变量,然后才能在 lambda 表达式中使用该变量。下面的示例演示这些规则:

// #region << 版 本 注 释 >> // /*---------------------------------------------------------------- // // Copyright (C) 2019 极客部落 // // 版权所有。 // // // // 文件名:Program.cs // // 文件功能描述: // // // // // // 创建者:GeekTribe // // 时间:14:05 // //----------------------------------------------------------------*/ // #endregion using System; ​ namespace MSN { delegate bool D(); delegate bool D2(int i); class MainClass { D del; D2 del2; public void TestMethod(int input) { int j = 0; ​ del = () => { j = 10; return j > input; }; ​ del2 = (x) => { return x == j; }; ​ Console.WriteLine("j={0}", j); ​ bool boolResult = del(); ​ Console.WriteLine("j = {0}. b = {1}", j, boolResult); } ​ public static void Main(string[] args) { MainClass mc = new MainClass(); mc.TestMethod(5); // Prove that del2 still has a copy of // local variable j from TestMethod。 bool result = mc.del2(10); ​ // Output: True Console.WriteLine(result); } } } ​ 

下列规则适用于 lambda 表达式中的变量范围:

1.捕获的变量将不会被作为垃圾回收,直至引用变量的委托符合垃圾回收的条件。

2.在外部方法中看不到 lambda 表达式内引入的变量。

3.Lambda 表达式无法从封闭方法中直接捕获 ref 或 out 参数。

4.Lambda 表达式中的返回语句不会导致封闭方法返回。如果跳转语句的目标在块外部,则 lambda 表达式不能包含位于 lambda 函数内部的 goto 语句、break 语句或 continue 语句。同样,如果目标在块内部,则在 lambda 函数块外部使用跳转语句也是错误的。

(4)Lambda Func

许多标准查询运算符都具有输入参数,其类型是泛型委托系列 Func<T, TResult> 中的一种。这些委托使用类型参数来定义输入参数的数量和类型,以及委托的返回类型。 Func 委托对于封装用户定义的表达式非常有用,这些表达式将应用于一组源数据中的每个元素。例如,请考虑以下委托类型:

public delegate TResult Func<TArg0, TResult>(TArg0 arg0) 

可以将委托实例化为 Func<int,bool> myFunc,其中 int 是输入参数,bool 是返回值。返回值始终在最后一个类型参数中指定。

Func<int, string, bool> 定义包含两个输入参数(int 和 string)且返回类型为 bool 的委托。当调用下面的 Func 委托时,该委托将返回 true 或 false 以指示输入参数是否等于 5:

Func<int, bool> myFunc = x => x == 5; ​ bool result = myFunc(4); // returns false of course 

今天的分享到此就结束了,感谢您的阅读,如果确实帮到您,您可以动动手指转发给其他人。

上一篇

已是最后文章

下一篇

已是最新文章

发表回复