前言
本章为高级C#技术的第二节也是最后一节。前一节在下面这个链接
高级C#技术https://blog.csdn.net/qq_71897293/article/details/134930989?spm=1001.2014.3001.5501
匿名类型
匿名类型如其名,匿名的没有指定变量的具体类型。
举个例子:
1 创建一个类
public class Student
{
public int Age { get; set; }
public int Weight { get; set; }
public string Name { get; set; }
}
2 创建一个Student的实例化,并且将这个类的所有成员进行了一个初始化
Student student = new Student
{
Age = 10,
Weight = 100,
Name = "",
};
示例解释:当前代码的写法是上一节提到的对象初始化器。
创建一个匿名类型:
var Variable = new
{
Age = 10,
Weight = 100,
Name = "",
};
示例解释:
1 使用了var 关键字 ,因为匿名函数没有可以使用的标识符(类型)
2 new 关键字后没有举明具体类型,等于是告诉编译器我们需要使用匿名类型
3 在系统的智能提示中,我们鼠标放在变量类型上都会显示相应的变量类型。 而匿名类型则是用a来表示。图例:
注意:当前匿名类型不是创建了Student的实例,而是一个自定义的匿名类型两者没有关系。
使用匿名类型:
var s = Variable.Age;//可以通过创建的匿名类型,拿到匿名类型的成员,但这些数据是只读的
注意:可以通过创建的匿名类型,拿到匿名类型的成员,但这些数据是只读的
扩展(此处引入书上源码):
var animals = new[]
{
new { Name = "Benjamin", Age = 42, Weight = 185 },
new { Name = "Benjamin", Age = 42, Weight = 185 },
new { Name = "Andrea", Age = 46, Weight = 109 }
};
WriteLine(animals[0].ToString());
WriteLine(animals[0].GetHashCode());
WriteLine(animals[1].GetHashCode());
WriteLine(animals[2].GetHashCode());
WriteLine(animals[0].Equals(animals[1]));
WriteLine(animals[0].Equals(animals[2]));
WriteLine(animals[0] == animals[1]);
WriteLine(animals[0] == animals[2]);
ReadKey();
示例解释:
当前的var animals=new [] 这里是使用了我们前面的类型推理,在当前这个匿名类型对象数组当中,我们创建了多个匿名类型。然后调用了几种不同的方法后输出结果。
GetHashCode:返回一个对象的哈希码(整数数值)可以用于储存在哈希表等数据结构中。在数组当中的前两个有着相同的属性值,即返回的哈希值是相同的。
Equals:匿名函数上就是比较一个对象的每个属性与另一个的对应属性值是否相同。如果相同则返回true。
==:比较比较对象引用。与方法Equals一致,但在匿名函数中的Equals不是一致。
输出结果:
{ Name = Benjamin, Age = 42, Weight = 185 }
-1144474565
-1144474565
-977821549
True
False
False
False
提问:创建匿名类型时,如果是是多个相同类型的参数,会创建多个匿名类型吗?像我们前面扩展中创建的方式。
答:不会,会创建同一个匿名类型的多个实例。
动态查找
动态查找:1 希望使用C#处理另一种语言创建的对象。2 处理未知类型的C#对象,如前一章使用了简单的反射来访问与类型关联的特性。
动态类型
动态类型我不好解释我们直接看它的定义方式,以及使用方式。
定义 (关键字dynamic) :
public dynamic dongtaitype;
注意:
上述代码我们就已经定义了一个动态类型,但它与我们前面的var关键字不同var的关键字它并不是一个类型,而我们这里定义的'dongtaitype'它叫动态类型。一旦有了动态类型我们就可以继续访问其他成员。
namespace MY
{
class Program
{
public static dynamic dongtaitype;
static void Main()
{
dongtaitype.ToString();
}
}
}
示例解释:我们这里直接用当前我们创建的动态类型直接调用了ToString方法。
注意:
我这里并没有实现ToString方法也并没有给我们的动态类型进行一个初始化。所以它现在的值是"null" 。但编译器并不会报错。编译器还是会在运行的时候去调用这个方法,但当前变量是null所以这无法调用,编译器会抛出异常。
警告:我们在使用动态类型的情况下,我们尽量在只能使用动态类型的情况下再去使用它。如非.net对象,我的理解就是非C#语言创建的对象
接下来我大家看一个动态类型的例子(引在书上示例):
using Microsoft.CSharp.RuntimeBinder;
using static System.Console;
namespace My
{
class MyClass1
{
public int Add(int var1, int var2) => var1 + var2;
}
class MyClass2 { }
class Program
{
static int callCount = 0;
static dynamic GetValue()
{
if (callCount++ == 0)
{
return new MyClass1();
}
return new MyClass2();
}
static void Main(string[] args)
{
try
{
dynamic firstResult = GetValue();
dynamic secondResult = GetValue();
WriteLine($"firstResult is: {firstResult.ToString()}");
WriteLine($"secondResult is: {secondResult.ToString()}");
WriteLine($"firstResult call: {firstResult.Add(2, 3)}");
WriteLine($"secondResult call: {secondResult.Add(2, 3)}");
}
catch (RuntimeBinderException ex)
{
WriteLine(ex.Message);
}
ReadKey();
}
}
}
示例解释:
当前我们定义了两个类一个是MyClass1 一个是MyClass2 。其中MyClass1 当中我们定义了一个Add的一个方法。MyClass2 我们没有定义任何成员。在程序主入口Main函数当中我们定义了两个动态类型并且调用了返回动态类型的方法。那么在我们第一次调用GetValue这个方法的时候它会为我们返回一个MyClass1 的实例。在我们第二次调用的时候会返回一个MyClass2 的实例。随后我们使用我们拿到的动态类型去调用我们的ToString方法在这个我们是可以调用的,它实际是一个类型的实例化,当然也可以调用我们父类的ToString方法。那么重要的是我们输出语句的第三个输出语句,我们调用了一个Add的方法我们当前第一个动态类型是没有问题。但是下面那个MyClass2 的实例。这个动态类型它是无法调用因为它根本就没有当前方法。所以这里我们会抛出一个异常会提示“My.MyClass2”未包含“Add”的定义。 然后我们再对抛出的异常进行一个打印异常消息随后这个代码就结束了
高级方法参数
高级方法参数:允许使用命名参数和可选参数对方法的一个使用和定义
可选参数
定义方法时某些参数是可选的,而并非像常见方法的参数,要进行重载才能实现相同效果。
示例:
using System;
using Microsoft.CSharp.RuntimeBinder;
using static System.Console;
namespace My
{
class Program
{
static void Main(string[] args)
{
WStudent("zhangsan", 15);
WStudent("李四");
ReadKey();
}
#region 重载实现
static void WrileStudent(string str)
{
WriteLine(str);
}
static void WrileStudent(string str, int age)
{
WriteLine(str + age);
}
#endregion
#region 可选参数
static void WStudent(string str, int age = 0)
{
WriteLine(str + age);
}
#endregion
}
}
示例解释:
当前我们给”WrileStudent“方法提供了两个重载的方法。当然我们在下面又定义了一个效果相同的一个方法。其中int age=0 我们在这里其实就是定义了一个可选参数。我们在需要使用它的时候则给它传值,不需要可以直接忽略不传。
1 可选参数的值:默认值必须是:字面值、常量值、默认值类型值。
2 Optional特性:我们也可以使用当前特性定义可选参数
static void WStudent(string str, [Optional] string ll, int age = 0)
{
WriteLine(str + age+ ll);
}
3 可选参数的顺序 :
- 可选参数必须位于方法参数列表的末尾
- 没有默认值的参数不能放在有默认值参数的后面
命名参数
//使用
static void Main(string[] args)
{
WStudent("李四", age: 10, ll: "");
ReadKey();
}
//定义
static void WStudent(string str, [Optional] string ll, int age = 0,int A=0,int B=10)
{
WriteLine(str + age + ll);
}
注意:使用方式时参数名+":"+值。
提示:
1 使用命令参数时可以指定需要的任意多个参数,而且参数的顺序是任意的。
2 命名参数也是可选的
3 当方法签名中有多个可选参数和一些必选参数时,可以先指定必选参数,然后再指定需要使用到的可选参数。
Lambda表达式
Lambda表达式是一种结构,简化C#编程的某些方面。Lambda表达式与其他的C#语言特性结合起来使用及其有用。如和匿名方法结合使用。
提示:匿名方法中 我们不要使用:My.lapsed+=Wrile; 这里是直接将一个方法赋值给事件。这样做会使代码更难理解也不清楚会发生什么
把lambda表达式用于匿名方法
在前面我们介绍了匿名方法的使用这里我将演示用lambda表达式替换匿名方法。在使用之前我有必要和你介绍lambda表达式的组成成分,分为三步。
第一部分:在括号当中放参数列表
第二部分:运算符
第三部分:我们的C#语句
举个例子:
using System;
using System.Timers;
namespace My
{
class Program
{
static void Main(string[] args)
{
Timer timer=new Timer(100);
timer.Elapsed += delegate (object sender, ElapsedEventArgs e)
{
//事件处理代码
};
//lambda 表达式替换匿名函数
timer.Elapsed += (sender, e) => Console.Write("Hello");
}
}
}
示例解释:
当前我们就做了一个lambda表达式替换匿名函数的一个示例。其中括号当中的就是我们的参数列表其他的部分也是按照我们上面所提到的三个部分所组成我们一个完整的lambda表达式
Lambda表达式的参数
Lambda表达式使用类型推理功能来确定所传递的参数类型。我们上述的一个示例并没有指定类型的情况下我们就是使用的它的一个类型推理功能,一个称为隐式类型化的Lambda表达式但如果我们指定了参数列表,当中的参数类型。那么它就被称为一个显示的Lambda表达式
举个例子:
//显式lambda 表达式
timer.Elapsed += (object sender, ElapsedEventArgs e) => Console.Write("Hello");
//隐式lambda 表达式
timer.Elapsed += (sender, e) => Console.Write("Hello");
注意:
1 在表达式当中你要么就全部显式类型要么就全部隐式类型。不能一个显式类型一个隐式类型
2 定义没有参数的 Lambda 表达式 则使用空括号来表达
举个例子:(sender) => Console.Write("Hello");
3 定义单个参数的 Lambda 表达式 则不需要用括号 sender => Console.Write("Hello");
Lambda表达式的语句体
我们其实可以在表达式的语句体中包含多个语句,为此我们就可以用花括号将我们的代码块放在其中。
举个例子:
timer.Elapsed += (object sender, ElapsedEventArgs e)=>
{
//放入代码块
};
当我们lambda表达式 需要返回除void类型的其他类型,那么我们就要用上关键字 return 进行返回值,和我们在方法中一样。
提示(引入书上原文):如果需要多个语句则定义一个单独的非并匿名方法来代替lambda表达式比较好。
Lambda表达式用作委托和表达式树
Lambda表达式其实是一个委托,你可以把Lambda表达式赋值给一个委托类型的变量。如我们前面的几个示例一样。但是在system名称空间中已经为我们提供了一些委托类型。所以在很多情况下我们都可以使用这些泛型委托类型而不必定义自己的泛型委托类型。
举例:
泛型类型 | 参数列表个数 | 返回类型 |
---|---|---|
Action表示Lambda表达式 | 不带参数 | Void |
Action<>表示Lambda表达式 | 最多八个参数 | Void |
Func<>表示Lambda表达式 | 最多八个参数 | 不是Void |