C#和WPF入门教程

目录

0. 来点鸡汤

时不时看看自己以前写的博客,感触很多。已经快一年多没有认真写博客了,今天重新开张,希望以此为契机,重拾生活的信心。
去年研究生毕业,去了北京,年薪拿到了30万。但只在山巅呆了四个月,便草草结束,离开北京各中原由无从说起,家庭和事业那时候只能选一个。虽然选了家庭,但现在想起北京的那份工作,也觉得可惜。离开北京之后,“安安心心”拿5000的月薪又快一年了,岗位也从原来的算法工程师变成程序员,这就是上帝给你开了个窗户,你刚看到一丝希望,谁知上帝开窗就给你一个大逼兜,然后关上窗户,留下你在风中凌乱。刚去北京时,我以为命运的齿轮开始转动,我真正的人生正式开始,原来只是去看了一遭那水中花、镜中月,留存脑海中的北京梦,与现在的满地鸡毛,讽刺啊。

哈哈哈,生活还要继续呢!原来京东买东西,现在拼多多、咸鱼也能凑合,玩算法换成写代码就当给自己夯实基础,领导原来是博士、硕士,现在的领导清一色中专,这不工作上更容易忽悠领导了吗?这样想,好多了。

听我叨逼叨那么多,哈哈,那我顺便说点关于C#的事情吧,也当做我成长的一个记录。这份教程不会每一步都去截图,如果你是小白,建议花个10分钟快速入门一下C#的基本概念,这个链接: C#菜鸟教程 带你快速入门。我写的虽然是WPF入门教程,但是C#遇到的问题我都会记录下来,直到把整个大楼给造好。

1. 概念

C# 怎么读? 读C井?读c星?哈哈
正确读音 C Sharp 音标: [ʃɑːrp]

1.1 C#能做什么

上位机软件、桌面显示软件、unity 3D游戏、网页开发等

1.2 为什么要选择C#,而不是QT或者其它?

(1)C# 简单易上手。qt 基本就C++的语法,用起来很复杂。
别扯什么运行速度,内存那些有的没的,那些东西全是扯犊子,对于新手或者绝大多数人,那些东西可能写一辈子代码也不用考虑,现在的计算机不缺算力和存储空间。主要精力应该是保证功能的实现和稳定运行。
(2)C# 是微软创造出来的,背靠宇宙第一强编辑器 visual studio,对于代码的调试,兼容,有着无可比拟的优势。
我举个例子,每台Windows电脑都有个事件查看器,它记录了电脑的各种异常事件。我们知道,写代码的时间是远远没有调试的时间长的,而用C#写的程序,通过Windows自带的事件查看器就能定位到异常代码是第几行,你就说这点,选不选C#。
(3)学会C# 会的是一类东西。
比如你是用C#写桌面应用程序(winform、WPF),你还可以用C#写网页 (asp.net),现在火热的Unity3D脚本也是通过C#来完成的,只要微软不跨,你说为啥不选一劳永逸的语言。

老子就不听傻逼博主的意见,我就要学qt。
宝啊,你看看我写的其他文章,也是鬼话连篇,但是我写博客没有糊弄各位宝,no copy,no paste.(是不是不知道啥意思,哈哈,快去百度翻译一下再和我犟)。如果你还是不选择C#,我只能画个圈诅咒你 —你写的代码如果有bug,永远也找不到。

1.3 winform 和 wpf有什么区别

C#有控制台应用程序Console,也有桌面应用程序(图形界面),现在主要就是用来展示数据的。
c#有两种方式写桌面应用程序:WPF、winform。我们来看看它们有什么不同。
在这里插入图片描述

  1. winform老,wpf新;
  2. winform窗体的控件属性是在C#里实现的,WPF则是在XAML里面实现的
  3. winform修改控件复杂,wpf简单
  4. winform入门简单,wpf入门难

宝啊,你读到此处想说什么?
什么傻逼博主,说了好像又啥都没说,都是些什么鬼啊?
我就想和你说,winform过时了,要学WPF。

1.4 .net Framework 和 .net Core联系

讲个小插曲,有一次其他部门的一个同事台上发言,原话:“我们这个程序是用 dot net core 开发的”,听到这句话时,我有点懵逼,什么人这是,你就说用C#开发的不就完了么,还tmd左一句dot net core,右一句dot net core,啥也不是。
在这里插入图片描述
看上面这张图片,我选的都是WPF,但是它们的架构不一样。

  1. .net Framework老, .net Core新
  2. .net Framework只针对windows平台,但包含了Windows平台的所有特性, .net Core 支持多个平台,但没有前者全面.如果要用xp系统,则使用framework是更好的选择

顺便说一嘴:咱可不是喜欢背后说人坏话,我批评甚至是鄙视我那个说dot net core 的同事,原因是他不写任何代码,也不会写,却总是装逼,咱讨厌这样的人。

1.5 WPF各个组成部分

在这里插入图片描述

分为XAML文件和cs文件,XAML文件用来处理界面,cs文件处理后台逻辑
App.xaml 指定系统启动界面,资源,引入的程序集

=======现在是 北京时间2023年9月27日 23:13,小傲娇的博主困了,不想写了

2. xaml

wpf有两部分构成,一部分是界面(前端设计),负责界面的设计和数据展示。另一部分是程序逻辑(后端),负责业务流程和数据处理。两部分相互独立,装逼的说法叫前后端分离。前端和后端会存在数据交互,可以通过事件或者绑定等方法来实现。

2.1 xaml中的对象和属性

=======现在是 北京时间2023年10月1日 20:41,小傲娇的博主今天没事,接着写。

xaml是xml的扩展,很多用法和想xml是一致的,如果你学过html,那应该会很快入门xaml,这东西只要入门了,写桌面应用程序布局时很爽,你用了这个,基本不会再用回winform了。

    1. 对象元素
      这里的对象和面向对象编程的概念基本一致。面向对象编程的对象是对一类具有相同属性的物体进行抽象,比如有个Dog类、CAT类,这里的对象是逻辑层面上的。
      xaml 中也有对象,比如按钮utton、文本框TextBox等都是对象,这里的对象是布局上的概念,更多是文法层面,不是逻辑上的概念。
      在WPF中,xmal代码也称作前端代码(控件),而以.cs结尾的代码叫做 “C#代码”(也叫后端代码或者代码隐藏)。
    <!--第一种写法-->
    <Button>按钮1</Button>
    
    <!--第二种写法-->
    <Button Content="按钮2"/>  
    
    1. 属性
      在C#中(其他编程语言也一样),一个类中往往有各种属性,比如学生类中有姓名属性、年纪属性等等。在xaml中,这个概念有点类似,比如有个按钮控件(按钮类),那它也有自己的属性,比如行高、行宽、背景颜色等等。下面这个示例,Grid.Row、Grid.Column都是属性。
    <Grid Grid.Row="2" Grid.Column="0" ShowGridLines="True">
    
    1. 属性元素
      属性元素是对象中的属性的属性,比如Button是一个对象,background是一个属性,对属性再设置就叫属性元素: SolidColorBrush
    <Button>
        <Button.Background >
            <SolidColorBrush Color="red"></SolidColorBrush>
        </Button.Background>
    </Button>
    

2.2 xaml页面布局

2.2.1 层级概念

  1. xaml设计是按照行和列的概念设计的;
  2. xaml是树形结构,在使用前先对Grid进行设计(几行几列);
  3. 在每个层级下面对界面进行设计
    在这里插入图片描述
    可能看了上面的图片也觉得一头雾水,我们现在从网页上随便截图一张,结合WPF看看到底什么是层级结构
    在这里插入图片描述
    我们把这个界面先整体分成三个区域, 即3行1列
    第1行第1列 所有的图标依次用stackPanel放置控件就可以了
    在第2行第1列 里面进一步切割,可在分成 2行1列
    在这里插入图片描述
    在第3行第1列 里面进一步切割,可在分成 1行3列
    在这里插入图片描述
    这样一个界面就被切割好了, 理解上面这个例子,应该就知道WPF层级的概念了

2.2.2 使用 Grid 定义行和列

根据上面层级的概念,将界面划分区域,接下来就可以用< Grid >来实现了. 下面的代码定义2行2列的布局

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="20"/>
        <RowDefinition/>          
    </Grid.RowDefinitions>
    
    <Grid.ColumnDefinitions>
        <ColumnDefinition/>
        <ColumnDefinition/>
    </Grid.ColumnDefinitions>
</Grid>

我们还可以在第2行第2列里再布置2行2列,代码实现如下

<Grid>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>

        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>

        <Grid Grid.Row="1" Grid.Column="1">
            <Grid.RowDefinitions>
                <RowDefinition/>
                <RowDefinition/>
            </Grid.RowDefinitions>

            <Grid.ColumnDefinitions>
                <ColumnDefinition/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
        </Grid>
    </Grid>

在这里插入图片描述

2.2.3 设置行和列

  • 绝对值

  • 按比例

  • auto

    <Grid ShowGridLines="True">
         
          <Grid.RowDefinitions>
              <RowDefinition Height="auto"/>
              <RowDefinition Height="80"/>
              <RowDefinition Height="2*"/>
          </Grid.RowDefinitions>
    
          <Grid.ColumnDefinitions>
              <ColumnDefinition/>
              <ColumnDefinition/>
              <ColumnDefinition/>
          </Grid.ColumnDefinitions>
    
          <Button Grid.Row="0" Grid.Column="0" Width="40" Height="40"/>
          <Button Grid.Row="1" Grid.Column="2"/>
    
      </Grid>
    

2.3 xaml样式

2.3.1 方法一:不给样式命名

直接在wpf控件前面写某类控件的样式,该方法不给样式起名字,后面使用该类控件时不需要引用,默认会使用该类样式。如下面代码,当我创建一个按钮是就会使用 <Window.Resources>里面的样式。

<Window x:Class="Wpf_test.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Wpf_test"
        mc:Ignorable="d"
        Title="MainWindow" Height="500" Width="700">

    <Window.Resources>
        <Style TargetType="Button">
            <Setter Property="Background" Value="#FF20B9E6"/>
            <Setter Property="Height" Value="50"/>
            <Setter Property="Width" Value="100"/>
        </Style>
    </Window.Resources>

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>

        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>

        <Button Content="按钮"/>
    </Grid>
</Window>

=======现在是 北京时间2023年10月1日 22:38,小傲娇的博主累了,不想写了

2.3.2 方法二:给样式命名

<Window.Resources>
    <Style x:Key="login" TargetType="Button">
        <Setter Property="Background" Value="#FF06C5F0"/>
        <Setter Property="Height" Value="50"/>
        <Setter Property="Width" Value="100"/>
    </Style>

    <Style x:Key="quit" TargetType="Button">
        <Setter Property="Background" Value="#FFF1F1F1"/>
        <Setter Property="Height" Value="50"/>
        <Setter Property="Width" Value="100"/>
    </Style>
</Window.Resources>

2.3.3 给样式命名同时继承基础样式

<Window.Resources>
    <!--基础样式-->
    <Style TargetType="Button">
        <Setter Property="FontSize" Value="20"/>
    </Style>
    <Style x:Key="login" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
        <Setter Property="Background" Value="#FF06C5F0"/>
    </Style>

    <!--继承基础样式 添加引用-->
    <Style x:Key="quit" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
        <Setter Property="Background" Value="#FFF1F1F1"/>
    </Style>
</Window.Resources>

2.4 在资源字典定义样式

在项目管理文件中添加一个资源字典Dictionary,对不同的控件进行样式设计. 在App.xaml中添加一个全局的资源字典, 将Dictionary的文件路径添加进去

2.4.1 添加资源字典

//在项目管理文件中添加一个资源字典Dictionary
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:WpfApp1">
    <!--新建一个全局资源字典-->
    
    <!--基础样式-->
    <Style TargetType="Button">
        <Setter Property="FontSize" Value="20"/>
    </Style>

    <!--继承基础样式 添加引用-->
    <Style x:Key="login" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
        <Setter Property="Background" Value="#FF06C5F0"/>
    </Style>

    <!--继承基础样式 添加引用-->
    <Style x:Key="quit" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
        <Setter Property="Background" Value="#FFF1F1F1"/>
    </Style>

</ResourceDictionary>

2.4.2 全局引用资源字典

<Application x:Class="WpfApp1.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:WpfApp1"
             StartupUri="Window1.xaml">
    <Application.Resources>
        <!--添加一个全局的资源资源字典-->
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="/BaseButtonStyle.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

2.5 控件模板重写

当你创建一个控件时,这个控件有自己的背景、颜色,这都是系统自定义好的,但是这并不是我们需要的,我们不可能每一次创建时都去修改,所以需要对控件模版改造,这个过程就就叫做控件模版重写。

<Grid>
    <Button Content="自定义按钮" Background="#FF00805D" Width="120" Height="100" BorderBrush="Black" BorderThickness="4">
        <Button.Template>
            <ControlTemplate TargetType="Button">
                <Border Name="bbb" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="15">
                    <TextBlock Text="{TemplateBinding Content}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
                </Border>

                <ControlTemplate.Triggers>
                    <Trigger Property="IsMouseOver" Value="True">
                        <Setter TargetName="bbb" Property="Background" Value="red"/>
                    </Trigger>

                    <Trigger Property="IsPressed" Value="True">
                        <Setter TargetName="bbb" Property="Background" Value="#FF10F3CA"/>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Button.Template>
    </Button> 
</Grid>

3. C# 代码语法规则

3.1 变量 、属性、字段分别是什么?

这个概念特别重要,搞不懂不行
类内部的私有变量即为字段,如代码中的变量 a;
属性向外暴露接口(公有部分),使外部能够通过属性访问内部字段, 属性本身不保存数据, 对属性操作实际是对属性对应字段操作; 如代码中对属性b操作,其实是对a操作;

/// <summary>
/// 定义一个变量(字段) a ,同时初始化
/// </summary>
private int a = 1;

/// <summary>
/// 定义属性 b
/// </summary>
public int b
{
    
    
    get {
    
     return a; }
    set {
    
     a = value; }
}

3.2 属性、变量、字段

在.NET中,属性(Properties)、字段(Fields)和变量(Variables)是编程中经常使用的术语,它们各自有不同的含义和用途。以下是这些术语的基本定义和区别:

变量(Variables):

  • 定义:变量是用来存储数据的标识符,它可以根据程序的需要存储不同类型的数据。
  • 访问:通过变量名直接访问。
  • 作用域:根据声明的位置(如方法内、类内、命名空间内等),变量的作用域会有所不同。
  • 示例:int number = 10;,这里number是一个整型变量。

字段(Fields):

  • 定义:字段是类的成员变量,它隶属于类而不是方法。字段通常是私有的,并且可以通过类的属性、方法或其他成员来访问。
  • 访问:通常通过类的实例访问,除非它们是静态的,则可以通过类名直接访问。
  • 作用域:字段的作用域通常是整个类。
  • 示例:
    public class MyClass
    {
          
          
        private int myField; // 这是一个私有字段
        
        public int MyProperty // 这是一个属性,下面会详细介绍
        {
          
          
            get {
          
           return myField; }
            set {
          
           myField = value; }
        }
    }
    

属性(Properties):

  • 定义:属性是类的成员,它提供了一种灵活的方式来读取、写入或计算私有字段的值。属性通常包含get和set访问器。
  • 访问:通过类的实例访问,就像访问字段一样,但实际上是在调用方法。
  • 作用域:属性的作用域通常是整个类,但可以通过get和set访问器进行更精细的控制。
  • 示例:继续上面的MyClass例子,MyProperty是一个属性,它有get和set访问器,允许外部代码读取和修改myField的值。
    public int MyProperty // 属性定义
    {
          
          
        get {
          
           return myField; } // get访问器,用于读取属性值
        set {
          
           myField = value; } // set访问器,用于设置属性值
    }
    

总结区别:

  • 变量是最基本的存储单元,而字段是类的变量成员。
  • 属性提供了一种更安全、更灵活的方式来访问类的字段。它们允许在读取或写入字段值之前执行额外的逻辑(例如验证)。
  • 通常建议将字段标记为private,并通过公共属性来访问它们,以实现封装和数据隐藏。这样可以确保对象状态的完整性和安全性。
  • 易错点

在C#中, 只能在类内定义变量和属性,同时允许对改变量或者属性进行初始化, 但不允许一个变量直接引用另外一个变量,如下所示,这是新人经常犯的一个错误。
在这里插入图片描述

3.3 set{}、get{}用法

在类Test中设置一个私有变量name,同时设置一个公有变量,通过公有变量name对私有变量NAME操作

    public class Test
    {
    
    
        /// <summary>
        /// 字段:一般私有,不对外开放, 首字母一般小写
        /// </summary>
        private string name;

        /// <summary>
        /// 属性:一般为共有,作为外部访问对应字段的一个接口, 首字母一般大写
        /// </summary>
        public string NAME
        {
    
    
            get {
    
     return name; }    //通过NAME返回name的值
            set
            {
    
    
                if (value == "keson")
                {
    
    
                    Console.WriteLine("hello,keson!");
                }
                else
                {
    
                       
                    name = value; //通过NAME设置name的值
                    Console.WriteLine("that is not keson,is "+ name);
                }    
            }
        }
    }

    class Program
    {
    
    
        static void Main()
        {
    
    
            Test test = new Test();
            test.NAME = "孙悟空";


        }
    }

3.4 App.config用法

App.config文件是系统默认的配置文件, 使用时需要添加引用 System.Configuration.dll, 该配置文件用于修改数据库连接字符串/窗口日志的信息.

App.config代码:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
    </startup>
  
  <appSettings>
    <add key="keyName" value="valueName"/>
    <add key="keyName2" value="valueName2"/>
    <add key="keyName3" value="valueName3"/>
  </appSettings>
</configuration>
  • C# 代码

    拿到配置文件里的内容

        public partial class MainWindow : Window
        {
          
          
            public MainWindow()
            {
          
          
                InitializeComponent();
                string settingValue = 					System.Configuration.ConfigurationManager.AppSettings["keyName"];
            }
        }
    

3.5 将一个类拆开写在多处

calss1.cs和class2.cs同属于一个类,c#允许一个类拆开写,这样防止写在一处,不方便阅读。

 
public partial class Window1 : Window
    {
    
    
        public Window1()
        {
    
    
            InitializeComponent();
        }
    }

public partial class Window1 : Window
    {
    
    
        public Window1()
        {
    
    
            fun();
        }    
}                      

4. 数据绑定

4.1 原理

通常是指将目标源(具有依赖属性的对象)绑定到 目标数据上(通常是控件)

数据绑定分为4种:
在这里插入图片描述

4.2 xaml 实现数据绑定

案例一

绑定源:代码隐藏 绑定目标: TextBox

    public partial class MainWindow : Window
    {
    
    
        
        public MainWindow()
        {
    
    
            InitializeComponent();            

            //设置窗体上下文对象   这里设置的是MyClass
            DataContext = new MyClass();
        }
    }

    public class MyClass
    {
    
    
        public int hight {
    
     get; set; } = 100;
        public int width {
    
     get; set; } = 101;
    }
<Grid>
    <StackPanel>
         <WrapPanel>
             <TextBlock Text="窗口的标题为:"/>
             <TextBox Text="{Binding Path=Title, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
         </WrapPanel>

         <WrapPanel>
             <TextBlock Text="窗口的高度为:"/>
             <!--上面MyClass里的hight-->
             <TextBox Text="{Binding hight}" Width="50" />  
         </WrapPanel>

         <WrapPanel>
             <TextBlock Text="窗口的宽度为:"/>
             <TextBox Text="{Binding width}" Width="50" />
         </WrapPanel>
     </StackPanel>
</Grid>

案例二

绑定源:TextBox 绑定目标: TextBox

<TextBox Grid.Row="1" Name="textBox_receiveData" Text={BindingPath=Text,ElementName=tbx2 }">

</TextBox>

4.3 C#代码实现数据绑定

public MainWindow()
{
    
    
   InitializeComponent();

   BindingData();
}

private void BindingData()
{
    
    
   //1 创建绑定对象
   var binding = new Binding("Text");
   //2 设置绑定源
   binding.Source = this.keson;
   //3 设置绑定目标
   keson_copy.SetBinding(TextBlock.TextProperty, binding);
}
<StackPanel>
    <WrapPanel>
        <TextBox Name="keson" Text="永远滴神"/>
    </WrapPanel>

    <WrapPanel>
        <TextBlock Name="keson_copy" />
    </WrapPanel>
</StackPanel>
namespace WpfApp1
{
    
    
    /// <summary>
    /// Window1.xaml 的交互逻辑
    /// </summary>
    public partial class Window1 : Window
    {
    
    
        LoginModel loginModel = new LoginModel();

        public Window1()
        {
    
    
            InitializeComponent();
            
            //数据绑定
            this.DataContext = loginModel;
            loginModel.user_name = "keson";
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
    
    
            图书馆主界面 lib = new 图书馆主界面();
             
            if(loginModel.user_name == "张三")   //这里的textbox_userName就是xaml里的x:Name
            {
    
    
                lib.Show();
            }
            else
            {
    
    
                MessageBox.Show("账号错误");
                loginModel.user_name = "";   //账号不是"张三"时,textbox里的内容会清空
            }
            
        }
    }

    public class LoginModel: INotifyPropertyChanged
    {
    
    
        private string _user_name;
        public string user_name
        {
    
    
            get {
    
     return _user_name; }
            set
            {
    
    
                _user_name = value;
                RaisePropertyChanged("user_name");
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;       //1 申明一个事件

        private void RaisePropertyChanged(string propertyName)
        {
    
    
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));//3 事件绑定一个方法            
        }

    }
}
<Grid Grid.Row="1" Grid.Column="0">
    <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
        <TextBlock Text="账号:"   FontSize="40" Foreground="#FF000605" HorizontalAlignment="Center" VerticalAlignment="Center"/>
        <TextBox Text="{Binding user_name}" x:Name="textbox_userName" Width="300" Background="White" Margin="10,10"  FontSize="40" Foreground="Black" HorizontalAlignment="Center" VerticalAlignment="Center"/>
    </StackPanel>            
</Grid>

4.4 PropertyChanged实现数据绑定

(1)原理

在这里插入图片描述

(2) 界面

(3) xaml

<Window x:Class="WpfApp2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp2"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800" MinHeight="40">

    <StackPanel >
        <TextBox Name="tbx" Height="50"  Margin="5" Text="TextBox1"/>
        <TextBox Name="tbx2" Height="50"  Margin="5" Text="TextBox2" TextChanged="Tbx2_TextChanged"/>
        
    </StackPanel>
</Window>

(4) c#

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WpfApp2
{
    
    
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
    
    
        Student stu;
        public MainWindow()
        {
    
    
            
            InitializeComponent();

            stu = new Student();

            // 准备Binding
            Binding binding = new Binding();
            binding.Source = stu;
            binding.Path = new PropertyPath("Name");

            //使用Binding连接数据源于Binding目标
            BindingOperations.SetBinding(tbx, TextBox.TextProperty, binding);
        }


        private void Tbx2_TextChanged(object sender, TextChangedEventArgs e)
        {
    
    
            stu.Name = tbx2.Text;
        }
    }

    class Student : INotifyPropertyChanged
    {
    
    
        public event PropertyChangedEventHandler PropertyChanged;

        private string name;

        public string Name
        {
    
    
            get {
    
     return name; }
            set
            {
    
    
                name = value;
                if(PropertyChanged != null)
                {
    
    
                    PropertyChanged.Invoke(this,new PropertyChangedEventArgs("Name"));
                }
            }
        }
    }

}

5 c#中的委托

5.1 如何理解委托?

这个名词困惑了了我好久,应该对比C++中的函数指针(一个指向函数的指针),理解了函数指针就理解委托了.
函数指针的就是一个指针变量指向函数,例子如下,定义了一个*pf的函数指针,入参是两个int,返回值也是int,将pf的地址指向max函数,这样pf也能使用max函数.

int max(int a,int b){
    return a>b?a;b;
}	
int (*pf)(int, int);	
pf = max;

不同的是委托可以挂载多个方法。
委托是一个容器,这个容器可以挂载不同的方法。先简单说如何使用:定义一个委托,并给改委托挂载方法,执行委托。
具体步骤:

  1. 声明委托;
  2. 创建委托实例;
  3. 委托绑定方法;
  4. 调用委托
public delegate int Calculate(int a, int b); //委托相当于函数指针

/// <summary>
/// 为委托创建一个加法计算方法
/// </summary>
/// <param name="name"></param>
public static int addMethod(int a, int b)
{
    
    
    return a + b;
}

/// <summary>
/// 为委托创建一个乘法计算方法
/// </summary>
/// <param name="name"></param>
public static int MutilMethod(int a, int b)
{
    
    
    return a * b;
}
static void Main(string[] args)
{
    
    

    /// <summary>
    /// 将加法计算方法赋值给委托
    /// </summary>
    Calculate cal = addMethod;
    cal += MutilMethod;

    //调用委托(依次调用)
    int a = cal(22, 2);            
    int b = cal(2, 9);
    int c = cal(22, 2);
    int d = cal(2, 9);
    Console.WriteLine(a);
    Console.WriteLine(b);
    Console.WriteLine(c);
    Console.WriteLine(d);
            
            

运行结果:

44
18
44
18
请按任意键继续. . .

5.2 系统自带的两种委托 Action<> 和Func<>

Action<> 用于挂载无返回值的方法
Func<> 用于挂载有返回值的方法

static void Main(string[] args)
        {
    
    
            delegateFun DF = F1;
            DF();

            //内置的委托

            //1 指向无返回值方法
            Action a = F1;
            a();


            //2 指向有返回值方法
            Func<int, int, int> func = Paremeters_fun;
            Console.WriteLine("2 指向有返回值方法,返回值为:" + func(2, 6));

            //3 指向无返回值无入参 匿名方法
            Action a2 = delegate ()
            {
    
    
                Console.WriteLine("3 指向无返回值无入参 匿名方法");
            };
            a2();

            //4 指向无返回值有入参 匿名方法
            Action<int,string> a3 = delegate (int i,string s)
            {
    
    
                Console.WriteLine($"4 指向无返回值有入参 匿名方法: i = {
      
      i},s = {
      
      s}");
            };
            a3(1,"keson");

            //5 指向有返回值方法 匿名方法
            Func<int, int, int> func2 = delegate (int i, int j)
            {
    
    
                return i + j;
            };
            Console.WriteLine("5 指向有返回值方法 匿名方法: " + func2(50, 6));

            //6 指向有返回值lamda表达式
            Func<int, int, int> func3 =(int i, int j)=>
            {
    
    
                return i + j;
            };
            Console.WriteLine("6 指向有返回值lamda表达式:" + func3(50, 6));

            //7 指向有返回值lamda表达式(省略入参类型)
            Func<int, int, int> func4 = (i,j) =>
            {
    
    
                return i + j;
            };
            Console.WriteLine("7 指向有返回值lamda表达式(省略入参类型):" + func3(50, 6));          

        }

5.3 简写委托的形式

委托经常伴随着Lambda表达式, Lambda表达式变化很多,有时候看到别人用的时候经常觉得莫名奇妙,我们从基本的开始,看看他们是一步步被简化的.

static void Main(string[] args)
{
    
    
    int[] nums = {
    
     11, 52, 69, 33, 54, 2, 9, 23, 66, 45 };
    //IEnumerable<int> result = nums.Where(a=>a > 10);

    //演变顺序
    // 1 先写一个匿名方法
    Action<int> action = delegate (int i) {
    
     Console.WriteLine(i); };
    // Action<int> action2 = delegate (i) { Console.WriteLine(i); };   //匿名方法数据类型不能省略
    // Action<int> action3 = delegate (int i)  Console.WriteLine(i); ;  //匿名方法大括号不能省略

    // 2 换成lamada表达式
    Action<int> action4 = (int i) => {
    
     Console.WriteLine(i); };  //完整写法
    Action<int> action5 = (i) => {
    
     Console.WriteLine(i); };      //数据类型省略
    Action<int> action6 = (i) => Console.WriteLine(i); ;        //大括号省略
    Action<int> action7 = (i) => Console.WriteLine(i);          //分号省略
    Action<int> action8 = i => Console.WriteLine(i);          //入参只有一个时,入参的小括号省略

    //3 如果是有返回值
    Func<int, bool> func1 = delegate (int i)     //先写个匿名方法
    {
    
    
        if (i > 0)
            {
    
     return true; }
        else
        {
    
    
            return false;
        }
    };

    Func<int, bool> func2 = (int i) =>      //换成lamada表达式
    {
    
    
        if (i > 0)
        {
    
     return true; }
        else
        {
    
    
            return false;
        }
    };

    Func<int, bool> func3 = (i) =>      //数据类型省略
    {
    
    
        if (i > 0)
        {
    
     return true; }
        else
        {
    
    
            return false;
        }
    };

    Func<int, bool> func4 = (i) =>      //优化返回代码
    {
    
    
        return i > 0;
    };

    Func<int, bool> func5 = (i) =>      //返回语句只有一条语句,省略大括号和return关键字
    
         i > 0;

    Func<int, bool> func6 = i =>      //入参只有一个,省略入参的括号

         i > 0;

}						

C#中的事件

  1. 在类内声明委托
  2. 在类内声明事件
namespace ConsoleApp1
{
    
    
    /// <summary>
    /// 事件发布者
    /// </summary>
    public class GreetingManager
    {
    
    
        /// <summary>
        /// 声明一个委托变量
        /// </summary>
        /// <param name="name"></param>
        public delegate void display(string name);

        /// <summary>
        /// 声明一个事件
        /// </summary>
        public event display display_event;  //相当于对委托类型的变量进行封装

        /// <summary>
        /// 创建一个处理方法
        /// </summary>
        /// <param name="name"></param>
        public void show(string name)
        {
    
    
            if (display_event != null)
            {
    
    
                display_event(name);
            }
        }
    }

    /// <summary>
    /// 订阅者
    /// </summary>
    public class GreetWays
    {
    
    
        public void EnglishGreeting(string name)
        {
    
    
            Console.WriteLine("good moring, " + name);
        }
        public void ChineseGreeting(string name)
        {
    
    
            Console.WriteLine("早上好, " + name);
        }
    }


    class Program
    {
    
    
        static void Main()
        {
    
    
            GreetingManager greetingManager = new GreetingManager();
            GreetWays greetWays = new GreetWays();

            greetingManager.display_event += greetWays.EnglishGreeting;  //事件绑定方法
            greetingManager.display_event += greetWays.ChineseGreeting;
            greetingManager.show("keson"); //调用了show就触发了display_event事件
            Console.ReadKey();
        }
    }
}


        static void Main(string[] args)
        {
    
    
            int[] nums = {
    
     11, 52, 69, 33, 54, 2, 9, 23, 66, 45 };
            //IEnumerable<int> result = nums.Where(a=>a > 10);

            //演变顺序
            // 1 先写一个匿名方法
            Action<int> action = delegate (int i) {
    
     Console.WriteLine(i); };
            // Action<int> action2 = delegate (i) { Console.WriteLine(i); };   //匿名方法数据类型不能省略
            // Action<int> action3 = delegate (int i)  Console.WriteLine(i); ;  //匿名方法大括号不能省略

            // 2 换成lamada表达式
            Action<int> action4 = (int i) => {
    
     Console.WriteLine(i); };  //完整写法
            Action<int> action5 = (i) => {
    
     Console.WriteLine(i); };      //数据类型省略
            Action<int> action6 = (i) => Console.WriteLine(i); ;        //大括号省略
            Action<int> action7 = (i) => Console.WriteLine(i);          //分号省略
            Action<int> action8 = i => Console.WriteLine(i);          //入参只有一个时,入参的小括号省略

            //3 如果是有返回值
            Func<int, bool> func1 = delegate (int i)     //先写个匿名方法
            {
    
    
                if (i > 0)
                    {
    
     return true; }
                else
                {
    
    
                    return false;
                }
            };

            Func<int, bool> func2 = (int i) =>      //换成lamada表达式
            {
    
    
                if (i > 0)
                {
    
     return true; }
                else
                {
    
    
                    return false;
                }
            };

            Func<int, bool> func3 = (i) =>      //数据类型省略
            {
    
    
                if (i > 0)
                {
    
     return true; }
                else
                {
    
    
                    return false;
                }
            };

            Func<int, bool> func4 = (i) =>      //优化返回代码
            {
    
    
                return i > 0;
            };

            Func<int, bool> func5 = (i) =>      //返回语句只有一条语句,省略大括号和return关键字
            
                 i > 0;

            Func<int, bool> func6 = i =>      //入参只有一个,省略入参的括号

                 i > 0;


        }
        ```
# C#不同界面之间互相操作控件

- 主界面可以操作子界面,子界面不可以操作主界面

```csharp
//主界面窗口
public partial class Form1 : Form
{
    
    
    public delegate void Del_main();
    static public Form1 form1 = new Form1();        

    public Form1()
    {
    
    
        form1 = this;
        InitializeComponent();            
    }
    
    private void button_ok_Click(object sender, EventArgs e)
    {
    
    
        Form2.form2.Show();
    }

    private void Form1_FormClosing(object sender, FormClosingEventArgs e)
    {
    
    
        Form2.form2.Close();
        Environment.Exit(0);
    }
}
public partial class Form2 : Form
{
    
         
    static public Form2 form2 = new Form2();
    public Form2()
    {
    
    
        form2 = this;
        InitializeComponent();            
    }
    
    private void form2_button_Click(object sender, EventArgs e)
    {
    
    
        Update_textBox_rcvCanData("测试");
    }
    private void Update_textBox_rcvCanData(string text)
    {
    
    
        if (Form1.form1.textBox.InvokeRequired)
        {
    
    
            //确保是在UI线程调用控件
            Form1.form1.textBox.Invoke(new Action<string>(Update_textBox_rcvCanData), text);
            
            return;
        }
        Form1.form1.textBox.AppendText(text);
    }

}

Lambda表达式

在.NET Core中,Lambda表达式是一种简洁的代码块表示形式,通常用于创建匿名方法。Lambda表达式可以包含表达式和语句,并且可用于创建委托或表达式树类型。Lambda表达式在LINQ查询、集合操作、排序、事件处理等方面非常有用。

Lambda表达式的基本语法如下:

(parameter list) => expression

其中,(parameter list) 是参数列表,expression 是Lambda主体表达式。

以下是一些在.NET Core中使用Lambda表达式的常见用法示例:

List<int> numbers = new List<int> {
    
     1, 2, 3, 4, 5 }; //数据源
  1. 作为参数传递给方法:
List<int> numbers = new List<int> {
    
     1, 2, 3, 4, 5 };
int evenCount = numbers.Count(n => n % 2 == 0); // 使用Lambda表达式作为Count方法的参数来计算偶数数量
  1. 用于LINQ查询:
var filteredNumbers = numbers.Where(n => n > 3); // 使用Lambda表达式过滤大于3的数字
  1. 用于排序集合:
var sortedNumbers = numbers.OrderBy(n => n); // 使用Lambda表达式按升序对数字进行排序
  1. 作为事件处理程序:
button.Click += (sender, e) => {
    
     /* 处理点击事件的代码 */ }; // 使用Lambda表达式作为事件处理程序订阅按钮的Click事件
  1. 创建委托实例:
Func<int, int> square = x => x * x; // 使用Lambda表达式创建委托实例,该委托接受一个int参数并返回其平方
int result = square(4); // 调用委托实例计算4的平方并获取结果
  1. 在并行编程中使用:
Parallel.For(0, numbers.Count, i => {
    
     /* 并行处理的代码 */ }); // 使用Lambda表达式定义并行循环中的操作逻辑

这些是.NET Core中Lambda表达式的常见用法示例。通过使用Lambda表达式,你可以以简洁、可读性强且功能强大的方式表示代码块,并在各种场景中应用它们。

MVVM

  • M model 数据模型

  • V View 界面

  • VM ViewModel 整合业务

WPF 定时器

(System.Threading.Timer)的使用

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

using System.Windows.Threading; 
//using System.Timers;
using System.Threading;

namespace WpfApp2
{
    
            
    private Timer timer;    
    public partial class MainWindow : Window
    {
    
    
        public MainWindow()
        {
    
             
            InitializeComponent();
            timer = new Timer(new TimerCallback(timerCall), null, 0, 5000);
        }
        
        private void timerCall(object state)
        {
    
    
            this.Dispatcher.BeginInvoke(new Action(() =>
            {
    
    
                tbx3.AppendText("定时器时间到 ");
            }));
        }
    }
}

配置文件读取

public partial class MainWindow : Window
    {
    
    
        public string path;
        public MainWindow()
        {
    
    
            InitializeComponent();

            path = System.AppDomain.CurrentDomain.BaseDirectory + "Config.ini";
            CreateFile(path);
            ReadConfigFile(path);
        }

        Dictionary<string, string> DictInitData = new Dictionary<string, string>();
        public void ReadConfigFile(string path)
        {
    
               
            //读取配置文件并保存到字典中
            StreamReader sr = new StreamReader(path);
            string line;
            while ((line = sr.ReadLine()) != null)
            {
    
    
                line = line.Trim();
                if (line.StartsWith("#"))
                {
    
    
                    continue;
                }

                if (!string.IsNullOrEmpty(line) && line.Contains("="))
                {
    
    
                    int equalsIndex = line.IndexOf('=');
                    if (equalsIndex > 0)
                    {
    
    
                        string key = line.Substring(0, equalsIndex).Trim();
                        string value = line.Substring(equalsIndex + 1).Trim();
                        DictInitData.Add(key, value);
                    }
                }               
            }
            sr.Close();

            if (DictInitData.Count != 3)
            {
    
    
                File.Delete(path);
                CreateFile(path);
            }

            
        }

        private void CreateFile(string path)
        {
    
    
            //判断配置文件是否存在,不存在创建一个
            if (!File.Exists(path))
            {
    
    
                StreamWriter sw = File.CreateText(path);
                sw.WriteLine("ip=192.168.0.1");
                sw.WriteLine("port=80");
                sw.Flush();
                sw.Close();                
            }
        }


    }

UI线程

        private void Update_textBox_rcvGuideData(string text)
        {
    
    
            if (this.IsHandleCreated)
                textBox_rcvGuideData.BeginInvoke(new Action(() =>
                {
    
    
                    textBox_rcvGuideData.AppendText(text + "\r\n");
                    if (textBox_rcvGuideData.Lines.Length > 500)
                    {
    
    
                        textBox_rcvGuideData.Clear();
                    }
                }));
        }

辨识attribute和property

  • property

property针对类和对象来说(C#代码),下面这个类中的name,fun2都叫property

class Person()
{
    
    
    string name;
    int age;
    void fun1(){
    
    
        //吃饭
    }
        void fun2(){
    
    
        //睡觉
    }
}
  • attribute

attribute是编程语言文法层面的东西(针对xaml来说),比如button按钮的宽度/高度

链接: link

单例:加深理解静态成员和方法

宝啊,你有没有想过什么是单例?类似单身狗吗?茕茕孑立,形影相吊,这好像不太正确,还有影子陪着单身汪的呀!
一个类只能创建一个实例(有啥用?鬼知道哩,反正我没用过)我写这个主要是帮着我理解C#的关键字 ”static“。

internal class Singleton
{
    
    
    private static Singleton uniqueInstance;
    private static readonly object lockObject = new object();
    private string? name;
    private int age;

    private Singleton(string name,int age)
    {
    
    
        this.name = name;
        this.age = age;
    }

    public static Singleton GetInstance()
    {
    
    
        if (uniqueInstance == null)
        {
    
    
            lock (lockObject) 
            {
    
     
                if(uniqueInstance == null)
                {
    
    
                    uniqueInstance = new Singleton(name, age);
                }
            }
        }
        return uniqueInstance;
    }  
}

为啥上面的代码无论你实例化多少个对象,实际上只有一个对象,这是为什么?原因就是“static”,无论成员还是方法,加上这个关键字,在实例化的时候,都不属于实例本身,他们都属于类本身,换句话说,类中的静态成员和方法被所有类共享。
上面的uniqueInstance是一个静态变量,无论创建多少个实例,最后这些实例都是共享一个静态成员变量。

LINQ的用法

这部分内容借鉴了杨中科老师的很多东西,大家可以去b站找他的视频学习真的值得一看. 链接: 杨中科老师视频链接

LINQ中where用法和原理

static void Main(string[] args)
{
    
    
   int[] nums = {
    
     11, 52, 69, 33, 54, 2, 9, 23, 66, 45 };

   // 1. 调用系统的方法
   IEnumerable<int> result = nums.Where<int>(a => a > 20 && a < 40);  //where后面的是lamada表达式的简写形式
   foreach(var i in result)
   {
    
    
       Console.WriteLine(i);
   }
   Console.WriteLine(" ");

   // 2. 调用自己写的方法1
   IEnumerable<int> result2 = SelectNums(nums, a => a > 20);
   foreach (var i in result2)
   {
    
    
       Console.WriteLine(i);
   }
   Console.WriteLine(" ");

   // 3. 调用自己写的方法2
   IEnumerable<int> result3 = SelectNums2(nums, a => a > 30);
   foreach (var i in result3)    //foreach (var i in SelectNums2(nums, a => a > 20))
   {
    
    
       Console.WriteLine(i);
   }

}

static IEnumerable<int> SelectNums(IEnumerable<int> items, Func<int, bool> func)
{
    
    
   List<int> list = new List<int>();
   foreach(int item in items)
   {
    
    
       if(func(item) == true)
       {
    
    
           list.Add(item);
       }
   }
   return list;
}

static IEnumerable<int> SelectNums2(IEnumerable<int> items, Func<int, bool> func)
{
    
    
   foreach (int item in items)
   {
    
    
       if (func(item) == true)
       {
    
    
           yield return item;
       }
   }
}

LINQ常用扩展方法

        static void Main(string[] args)
        {
    
    
            List<Employee> list = new List<Employee>();
            list.Add(new Employee {
    
     Id = 1, Name = "张三", Age = 50, Gender = true, Salary = 50000 });
            list.Add(new Employee {
    
     Id = 2, Name = "李四", Age = 40, Gender = false, Salary = 90000 });
            list.Add(new Employee {
    
     Id = 3, Name = "赵六", Age = 30, Gender = false, Salary = 30000 });

            IEnumerable<Employee> employee = list.Where((e) => {
    
     return e.Age > 30; });   // employees.Where(e =>  return e.Age > 30 );
            foreach(var i in employee)
            {
    
    
                Console.WriteLine(i);
            }

            Console.WriteLine(list.Count(e => e.Salary > 50000));
            Console.WriteLine(list.Any(e => e.Gender == true));

            
            // 防御性编程
            Console.WriteLine(list.Single(e => e.Name == "张三"));
            // Console.WriteLine(list.Single(e => e.Name == "孙悟空"));//没有该条信息,会报异常
            Console.WriteLine(list.SingleOrDefault(e => e.Name == "孙悟空"));
        }
class Employee
    {
    
    
        public long Id {
    
     get; set; }
        public string Name {
    
     get; set; }
        public int Age {
    
     get; set; }
        public bool Gender {
    
     get; set; }
        public int  Salary{
    
     get; set; }

        /// <summary>
        /// C#所有的class和struct都会继承object,而每一个object都会有一个ToString的方法,这里重写该方法
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
    
    
            return $"Id={
      
      Id},Name={
      
      Name},Age={
      
      Age},Gender={
      
      Gender},Salary={
      
      Salary}";
        }
    }
class Dog
    {
    
    
        public string nickName {
    
     get; set; }
        public int age {
    
     get; set; }
        public override string ToString()
        {
    
    
            return $"nickName = {
      
      nickName},age={
      
      age}";
        }
        
    }
static void Main(string[] args)
        {
    
    
            List<Employee> list = new List<Employee>();
            list.Add(new Employee {
    
     Id = 1, Name = "张三", Age = 50, Gender = true, Salary = 50000 });
            list.Add(new Employee {
    
     Id = 2, Name = "李四", Age = 40, Gender = false, Salary = 90000 });
            list.Add(new Employee {
    
     Id = 3, Name = "赵六", Age = 30, Gender = false, Salary = 30000 });
            list.Add(new Employee {
    
     Id = 4, Name = "gati", Age = 34, Gender = true, Salary = 6000 });
            list.Add(new Employee {
    
     Id = 5, Name = "jim", Age = 40, Gender = false, Salary = 7000 });
            list.Add(new Employee {
    
     Id = 6, Name = "ancle", Age = 24, Gender = false, Salary = 2000 });

            Console.WriteLine("where:");
            IEnumerable<Employee> employee = list.Where((e) => {
    
     return e.Age > 30; });   // employees.Where(e =>  e.Age > 30 );
            foreach(var i in employee)
            {
    
    
                Console.WriteLine(i);
            }

            Console.WriteLine("count:");
            Console.WriteLine(list.Count(e => e.Salary > 50000));
            Console.WriteLine(list.Any(e => e.Gender == true));


            // 防御性编程
            Console.WriteLine("single:");
            Console.WriteLine(list.Single(e => e.Name == "张三"));
            // Console.WriteLine(list.Single(e => e.Name == "孙悟空"));//没有该条信息,会报异常
            Console.WriteLine(list.SingleOrDefault(e => e.Name == "孙悟空"));

            //排序 
            Console.WriteLine("order:");
            foreach (var i in list.OrderBy(e => e.Salary))
            {
    
    
                Console.WriteLine(i);
            }

            //跳过和取
            Console.WriteLine("skip and take:");
            foreach (var i in list.Skip(2).Take(30))
            {
    
    
                Console.WriteLine(i);
            }

            //分组
            Console.WriteLine("\r\n groupby:");
            var groupItem = list.GroupBy(e => e.Age);
            foreach(var i in groupItem)
            {
    
    
                Console.WriteLine(i.Key);
                foreach(var j in i)
                {
    
    
                    Console.WriteLine(j);
                }
                Console.WriteLine("");
            }

            //投影  类似数据库的select
            IEnumerable<int> age_select = list.Select(e => e.Age);   //把所有的年龄取出来
            IEnumerable<string> name_select = list.Select(e => e.Name);
            Console.WriteLine("\r\n 投影:");
            foreach (var i in age_select)
            {
    
    
                Console.WriteLine(i);
            }

            IEnumerable<Dog> dogs = list.Select(e => new Dog {
    
     nickName = e.Name, age = e.Age });
            Console.WriteLine("\r\n 投影dog类:");
            foreach (var i in dogs)
            {
    
    
                Console.WriteLine(i);
            }

            Console.WriteLine("\r\n 匿名类型:");
            var items = list.Select(e => new{
    
     姓名 = e.Name,性别 = e.Gender?"男":"女"});
            foreach (var i in items)
            {
    
    
                Console.WriteLine(i.姓名+"  "+i.性别);
            }

            //综合语法
            Console.WriteLine("\r\n 综合语法:");
            var items2 = list.GroupBy(e => e.Age).Select(g => new {
    
     年龄 = g.Key, MaxSalary = g.Max(e => e.Salary), minSalary = g.Min(e => e.Salary),人数 = g.Count()});
            foreach (var i in items2) 
            {
    
    
                Console.WriteLine(i. 年龄 + "  " + i.MaxSalary+ "  " + i.minSalary + "  " + i.人数);
               
            }
        }

            //类型转化
            IEnumerable<Employee> items = list.Where(e => e.Salary > 6000);
            List<Employee> L1 = items.ToList();
            Employee[] arry = items.ToArray();

            //链式语法
            Console.WriteLine("\r\n 链式语法:");
            var list2 = list.Where(e => e.Id > 2).GroupBy(e => e.Age).OrderBy(g => g.Key).Take(3).
                Select(g => new {
    
     年龄 = g.Key, 平均工资 = g.Average(e => e.Salary) });
            foreach(var i in list2)
            {
    
    
                Console.WriteLine(i.年龄 + " " + i.平均工资);
            }

split方法可能会掉入的陷阱

split会按照特定字符进行分割,同时返回分割后的字符数组.分割后一定要注意字符前后端是否存在空格,不然会掉入大坑.

通过Linq读取配置文件

  //配置文件接口类  
    public interface IConfigService
    {
    
    
        string GetValue(string name);
    }

//配置文件实现类  
public class IniFileConfig : IConfigService
    {
    
    
        public string FilePath {
    
     get; set; }
        public string GetValue(string name)
        {
    
    
            var kv = File.ReadAllLines(FilePath).Select(s => s.Split('=')).Select(strs => new {
    
     Name = strs[0].Trim(), value = strs[1].Trim() }).SingleOrDefault(keyValue => keyValue.Name == name);
            //var kv2 = kv.Select(s => s.Split('='));

            if (kv != null)
            {
    
    
                return kv.value;
            }
            else
            {
    
    
                return null;
            }
        }
    }

//调用配置文件
   IniFileConfig config = new IniFileConfig();
                string SmtpServer = config.GetValue("SmtpServer");
            string UserName = config.GetValue("UserName");
            string Password = config.GetValue("Password");

linq常见问题

//linq解决面试问题
int i = 4;
int j = 5;
int k = 6;
int[] nums = new int[] {
    
     i,j,k };

//linq求解
int max = nums.Max();

//math处理
int max2 = Math.Max(i, Math.Max(j, k));

//三元运算法
int max3 = (i = i > j ? i : j) > k ? i : k;
int max4 = i > j ? i > k ? i : k : j > k ? j : k;
Console.WriteLine(max4);

//求解平均值
string s = "1,2,3,4,5,6,7,8,9";
string[] str = s.Split(',');
IEnumerable<int> arry = str.Select(e => Convert.ToInt32(e));
Console.WriteLine(arry.Average());

var avg = s.Split(',').Select(p => Convert.ToInt32(p));

//统计字符串字母出现的频率
Console.WriteLine("\r\n 统计字符串字母出现的频率:");
string s1 = "hello world,keson,hgongnoring";
var items4 = s1.Where(c => char.IsLetter(c)).Select(c => char.ToLower(c)).GroupBy(c => c).Select(g => new {
    
     g.Key, count = g.Count() })
    .OrderByDescending(g=>g.count).Where(g=>g.count>2);
foreach(var item in items4)
{
    
    
    Console.WriteLine(item);
}

依赖注入

将一个类放到一个容器中,使用这个类时不需要实例化,直接从从容器中取出来

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Linq;
using Microsoft.Extensions.DependencyInjection;

namespace ConsoleApp1
{
    
    
    class Program
    {
    
    
        delegate void delegateFun();
        static void Main(string[] args)
        {
    
    
            /* 
             * 1 ServiceCollection是内置的反转控制容器;services.BuildServiceProvider是使用容器中服务
             * 2 使用步骤:创建控制容器;向容器中注册方法;使用容器的BuildServiceProvider向容器中获取服务
            */

            //注册服务
            ServiceCollection services = new ServiceCollection();
            //services.AddTransient<TestServiceImpl>();
            //services.AddSingleton<TestServiceImpl>();
            services.AddScoped<TestServiceImpl>();      //同一个scope里拿到的对象是一致的
            using (ServiceProvider sp = services.BuildServiceProvider())
            {
    
    
                TestServiceImpl testService = sp.GetRequiredService<TestServiceImpl>();
                testService.Name = "keson";
                testService.SayHello();

                TestServiceImpl testService2 = sp.GetRequiredService<TestServiceImpl>();
                //Console.WriteLine("testService, testService2:" + object.ReferenceEquals(testService, testService2));

                using (IServiceScope scope1 = sp.CreateScope())
                {
    
    
                    TestServiceImpl t = scope1.ServiceProvider.GetService<TestServiceImpl>();
                    t.Name = "张三";
                    t.SayHello();

                    TestServiceImpl t1 = scope1.ServiceProvider.GetService<TestServiceImpl>();
                    //Console.WriteLine("t, t1:" + object.ReferenceEquals(t, t1));
                    //Console.WriteLine("t, testService:" + object.ReferenceEquals(t, testService));
                }

                using (IServiceScope scope2 = sp.CreateScope())
                {
    
    
                    TestServiceImpl t = scope2.ServiceProvider.GetService<TestServiceImpl>();
                    t.Name = "李四";
                    t.SayHello();
                }
            }

            
            

        }
    }

    

    public interface ITestServic
    {
    
    
        string Name {
    
     get; set; }
        void SayHello();

    }

    public class TestServiceImpl : ITestServic,IDisposable
    {
    
    
        public string Name {
    
     get; set; }

        public void Dispose()
        {
    
    
            Console.WriteLine("disposable..........");
        }

        public void SayHello()
        {
    
    
            Console.WriteLine($"hi,my {
      
      Name}");
        }
    }

    public class TestServiceImpl2 : ITestServic
    {
    
    
        public string Name {
    
     get; set; }
        public void SayHello()
        {
    
    
            Console.WriteLine($"你好,我是{
      
      Name}");
        }
    }
}

依赖注入 : 接口 + 实现

直接从类中注入

在一个A类中使用另外一个B类,不需要new 一个新的B类对象,只需要在被注入对象A实例化时传入B类的接口即可。

//Program.cs
static void Main(string[] args)
{
    
    
    NotificationService notificationService = new NotificationService(new EmailSender());
    notificationService.UseSendMessage();
 }
 //--------------------------------------------
 public interface IMessageSender
{
    
    
    void SendMessage(string message);
}
 //--------------------------------------------
 public class EmailSender : IMessageSender
{
    
    
    public void SendMessage(string message)
    {
    
    
        Console.WriteLine(message);
    }
}
 //--------------------------------------------
 public class NotificationService
{
    
    
    private readonly IMessageSender messageSender;

    public NotificationService(IMessageSender messageSender)
    {
    
    
        this.messageSender = messageSender;
    }

    public void UseSendMessage()
    {
    
    
        messageSender.SendMessage("hello,keson");
    }
}

通过服务容器依赖注入

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Linq;
using Microsoft.Extensions.DependencyInjection;

namespace ConsoleApp1
{
    
    
    class Program
    {
    
    
        delegate void delegateFun();
        static void Main(string[] args)
        {
    
    
            /* 
             * 1 ServiceCollection是内置的反转控制容器;services.BuildServiceProvider是使用容器中服务
             * 2 使用步骤:创建控制容器;向容器中注册方法;使用容器的BuildServiceProvider向容器中获取服务
            */

            //注册服务
            ServiceCollection services = new ServiceCollection();
            services.AddScoped<ITestService, TestServiceImpl>();   // 接口 + 实现
            services.AddScoped<ITestService, TestServiceImpl2>();
            using (ServiceProvider sp = services.BuildServiceProvider())   //当然,你也可以用services.BuildServiceProvider(),这里用变量接一下方便用
            {
    
    
                //services.BuildServiceProvider().GetService<ITestServic>();
                ITestService ITest = sp.GetService<ITestService>();
                ITest.Name = "孙悟空";
                ITest.SayHello();
                //Console.WriteLine(ITest.GetType());

                ITestService ITest1 = sp.GetRequiredService<ITestService>();     //确定一定有这个服务
                IEnumerable<ITestService> ITest2 = sp.GetServices<ITestService>();   //获取多个服务

                foreach(var i in ITest2)
                {
    
    
                    Console.WriteLine(ITest2.GetType());
                }
            }
            

        }
    }    

    public interface ITestService
    {
    
    
        string Name {
    
     get; set; }
        void SayHello();

    }

    public class TestServiceImpl : ITestService,IDisposable
    {
    
    
        public string Name {
    
     get; set; }

        public void Dispose()
        {
    
    
            Console.WriteLine("disposable..........");
        }

        public void SayHello()
        {
    
    
            Console.WriteLine($"hi,I am {
      
      Name}");
        }
    }

    public class TestServiceImpl2 : ITestService
    {
    
    
        public string Name {
    
     get; set; }
        public void SayHello()
        {
    
    
            Console.WriteLine($"你好,我是{
      
      Name}");
        }
    }
}

依赖注入:通过构造函数

这里有一个小细节,所有实现的接口的实现必须是公有的

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Linq;
using Microsoft.Extensions.DependencyInjection;

namespace ConsoleApp1
{
    
    
    class Program
    {
    
    
        delegate void delegateFun();
        static void Main(string[] args)
        {
    
    
            ServiceCollection services = new ServiceCollection();
            services.AddScoped<Controller>();
            services.AddScoped<ILog, LogImpl>();
            services.AddScoped<IStorage, StorageImpl>();
            services.AddScoped<IConfig, ConfigImpl>();
            services.AddScoped<ILog, LogImpl>();

            using(var sp = services.BuildServiceProvider())
            {
    
    
                var c = sp.GetRequiredService<Controller>();
                c.Test();
            }
            Console.ReadKey();

            
        }
    }

    class Controller
    {
    
    
        private readonly ILog log;
        private readonly IStorage storage;
        public Controller(ILog log,IStorage storage)
        {
    
    
            this.log = log;
            this.storage = storage;
        }
        public void Test()
        {
    
    
            log.Log("开始上传");
            storage.Save(" 名字 ", " 上传的内容 ");
            log.Log("上传完毕");
            
        }
    }

    interface ILog
    {
    
    
        void Log(string msg);
    }

    class LogImpl : ILog
    {
    
    
        public void Log(string msg)
        {
    
    
            Console.WriteLine($"日志: {
      
      msg}");
        }
    }

    interface IConfig
    {
    
    
        string GetValue(string name);
    }

    class ConfigImpl:IConfig
    {
    
    
       public string GetValue(string name)
        {
    
    
            return "config file";
        }
    }

    interface IStorage
    {
    
    
        void Save(string name,string content);
    }

    class StorageImpl:IStorage
    {
    
    
        private readonly IConfig config;
        public StorageImpl(IConfig config)
        {
    
    
            this.config = config;
        }
        public void Save(string name,string content)
        {
    
    
            string server = config.GetValue("server");
            Console.WriteLine($"向服务器为: {
      
      server}的文件名为: {
      
      name}上传:{
      
      content}");
        }
    }
   
}

配置读取

配置容器

可能年纪大了,对于那种不讲人话的人,特别反感,包括但不限于:领导、同同事、同学、长辈、博主、水军、我自己。他们就一个特点,很简单的事情他们包装的面目全非,听得新人云里雾里,以彰显在别人眼里不值一提的优越感。
什么是配置容器?他就是一种管理配置文件的东西。什么是配置文件,举个例子,你想连接数据库,你得有账户名、密码、端口号等等,这些东西不可能每次登录都自己手动输入,也不可能都会提供输入这些信息的界面,那就得从文件中读取,我们把它叫做配置文件。
*本章的核心就是Configrution这个命名空间,*搞懂这个就ok了,主要包括两个点:怎么创建配置容器?如何通过配置容器读取配置文件。

步骤

  • 构建配置容器;

  • 向配置容器中添加配置文件(json,xml,txt);

  • 通过Build()读取配置文件;

ConfigurationBuilder builder = new();
builder.AddJsonFile("config.json",optional:true,reloadOnChange:true);
IConfigurationRoot configRoot = builder.Build();

json文件作为绑定源, 配置类作为绑定目标. 将配置类绑定到配置文件. 配置文件肯定得拿出来才能用, 如果一致放在json文件中,无法用,所以用一个和json配置文件相同的类来接受数据

//选项类被绑定到 ICconfigurationRoot 接口
Config config = new Config(); 
configurationRoot.Bind(config);

//选项类被绑定到 ICconfigurationRoot 接口 子类
Service service = new Service();
configurationRoot.GetSection("Service").Bind(service);

实例:数据源绑定单个类

该方式将类单个单个的配置,配置起来简单,但不利于集中管理

//json file
{
    
    
  "key1": "IamString",
  "key2": 10,
  "key3": true
}
//------------------------------------------------------------------------



using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.Json;


class Program
{
    
    
    static void Main(string[] args)
    {
    
    
        IConfigurationBuilder builder = new ConfigurationBuilder();
        builder.AddJsonFile("appsetting.json", optional: true, reloadOnChange: true);

        var configurationRoot = builder.Build();

               

        //选项类被绑定到 ICconfigurationRoot 接口
        Config config = new Config(); 
        configurationRoot.Bind(config);
        Console.WriteLine($"key1:{
      
      config.Key1}");
        Console.WriteLine($"key2:{
      
      config.Key2}");
        Console.WriteLine($"key3:{
      
      config.Key3}");

        //选项类被绑定到 ICconfigurationRoot 接口 子类
        Service service = new Service();
        configurationRoot.GetSection("Service").Bind(service);
        Console.WriteLine($"Service.Host {
      
      service.Host}");
        Console.WriteLine($"Service.Host {
      
      service.Port}");

        //

        Console.ReadKey();
    }
}

class Config
{
    
    
    public string Key1 {
    
     get; set; }

    public int Key2 {
    
     get; set; }

    public bool Key3 {
    
     get; set; }
}

class Service
{
    
    
    public string Host {
    
     get; set; }

    public string Port {
    
     get; set; }

    //不能注入私有属性
    //public string Port { get; private set; } = "999";
}

注册服务

步骤

  • 创建一个服务容器;

  • 容器对配置类进行注册服务;

    services.Configurede<Ttype>{lamda表达式};
    
  • 获取服务

serviceProvider.GetRequiredService<IOptions<MyOption>>();
//或者
serviceProvider.GetRequiredService<IOptionsSnapshot<Config>>();

实例:直接通过lambda表达式对类配置

using Microsoft.Extensions.Configuration;
//using Microsoft.Extensions.Configuration.Json;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using System;



var services = new ServiceCollection();

// 通过.Configure 对选项类进行配置(注册服务)
services.Configure<MyOption>(n =>
{
    
    
    n.A = "keson";
    n.B = "";
});

var serviceProvider = services.BuildServiceProvider();

//通过<IOptions<MyOption>>()获取服务
var myOption = serviceProvider.GetRequiredService<IOptions<MyOption>>();
MyOption myOptionValue = myOption.Value;
Console.WriteLine(myOptionValue.A);
Console.WriteLine(myOptionValue.B);

public class MyOption
{
    
    
    public string A {
    
     get; set; }

    public string B {
    
     get; set; }
}

实例:通过addOption配置

这种方法配置起来也很简单,且可以链式编程(集中配置,再一个地方对多个类绑定数据源)
addOption和IOptionsSnapshot区别:前者是刚运行时就保存在缓存中,后者是每次处理请求时都会重新从IOptionsSnapshot中读取,以便能够获取到最新的配置信息。

//--------------------------json文件-------------------------------
{
    
    
  "name": "keson",
  "age": "28",
  "proxy": {
    
    "address": "192.168.1.0","port": "80"}
}


//--------------------------main()-------------------------------
namespace 配置文件
{
    
    
    internal class Program
    {
    
    
        static void Main(string[] args)
        {
    
    
            ServiceCollection services = new();
            services.AddScoped<Controller>();

            ConfigurationBuilder builder = new();
            /*
             //通过json读取配置文件
             builder.AddJsonFile("config.json",optional:true,reloadOnChange:true);
            */

            //通过命令行读取配置文件 可以在调试中设置 不同参数用空格分开,不能有多余的空格
            builder.AddCommandLine(args);
            IConfigurationRoot configRoot = builder.Build();

            services.AddOptions().Configure<Config>(e => configRoot.Bind(e));
            

            using (var sp= services.BuildServiceProvider()) 
            {
    
    
                //通过一个Controller类来操控Config
                var c2 = sp.GetRequiredService<Controller>();
                c2.Test();

                //直接操控
                var c = sp.GetRequiredService<IOptionsSnapshot<Config>>();
                c.Value.name = "孙悟空";
                c.Value.age = "500";
                Console.WriteLine(c.Value.name);
                Console.WriteLine("--------------");
                Console.WriteLine(c.Value.age);
            }
        }
    }

    class Config
    {
    
    
        public string name {
    
     get; set; }
        public string age {
    
     get; set; }
        public Proxy proxy {
    
     get; set; }
    }

    class Proxy
    {
    
    
        public string address {
    
     get; set; }
        public int Port {
    
     get; set; }
    }
}

//--------------------------Controller-------------------------------
internal class Controller
    {
    
    
        //用于访问请求生存期的 TOptions 的值 
        private readonly IOptionsSnapshot<Config> optconfig;
        public Controller(IOptionsSnapshot<Config> optconfig)
        {
    
    
            this.optconfig = optconfig;
        }

        public void Test()
        {
    
    
            Console.WriteLine(optconfig.Value.name);
            Console.WriteLine("--------------");
            Console.WriteLine(optconfig.Value.age);
        }
    }

扁平化配置

name=如来 age=10000 proxy:address=1.1.1.1 proxy:port=9999 proxy:IDs:0=69 proxy:IDs:1=69

日志系统

日志级别

Trace -> Debug -> Informatio -> Warning -> Error -> Critical

日志记录到控制台

核心代码:

  • 引用扩展包

  • 将日志记录到控制台

  • 设置记录到控制台代码Log Level

using Microsoft.Extensions.Logging;  //记录日志用的扩展包   
loggingBuilder.AddConsole();      //将日志记录到控制台    
loggingBuilder.SetMinimumLevel(LogLevel.Trace);  //设置最低输出级别信息

完整代码

//----------------Test()类-------------------------
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace 日志系统
{
    
    
    internal class TestLogging
    {
    
    
        private readonly ILogger<TestLogging> logger;
        public TestLogging(ILogger<TestLogging> logger)
        {
    
    
            this.logger = logger;
        }

        public void Test()
        {
    
     
            logger.LogDebug("开始执行Logging");
            logger.LogWarning("程序警告");
            logger.LogError("程序失败");

            try
            {
    
    
                File.ReadAllText("A://");
                logger.LogDebug("读取文件成功");
            }
            catch (Exception e)
            {
    
    
                logger.LogError(e, "读取文件失败");   //将捕获的异常也打印出来
            }            
        }
    }
}

//----------------Main()-------------------------
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Console;
using 日志系统;

ServiceCollection services = new ServiceCollection();
services.AddLogging(loggingBuilder =>
{
    
    
    loggingBuilder.AddConsole();      //将日志记录到控制台    
    //loggingBuilder.AddEventLog();  //将日志记录到Windows日志事件中
    loggingBuilder.SetMinimumLevel(LogLevel.Trace);  //设置最低输出级别信息
    
});

services.AddScoped<TestLogging>();

using(var sp = services.BuildServiceProvider())
{
    
    
    TestLogging testLogging = sp.GetRequiredService<TestLogging>();
    testLogging.Test();
}

日志记录到文本: NLog

官网查看例程

很多时候我们不仅需要从控制台查看日志, 还需要从日志文件上查看, Microsoft.Extensions.Logging不满足需求, 需要引用 NLog.Extensions.Logging . 下面的例图是访问NLog.Extensions.Logging扩展包的方法

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

复制config代码,注意名字一定要是nlog.config , 存储位置也要修改,直接改为程序的根目录.

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Nlog核心代码

NLog是通过Xml实现配置的, 主要有两部分组成: target 和 rules, 先定义目标,通过规则匹配

target:

  • type: 输出到文件还是控制台
  • name: target的名字,就像你声明一个变量 int a = 5, 引用它的时候,就可以通过name引用.
  • filename: 输出文件名字

rules:

  • name: 匹配的名字,允许使用正则表达式,例如匹配命名空间的名字
  • minlevel: 需要记录的最低日志权限
  • wirteTo: 需要输出到的匹配的 target name
<targets>
	<target xsi:type="File" name="allfile" fileName="Test.log"/>    
	<target xsi:type="Console" name="lifetimeConsole" />
</targets>

<rules>
       	<logger name="*" minlevel="Trace" writeTo="allfile" />
		<logger name="SystemServices.*" minlevel="Warn" writeTo="lifetimeConsole" />
</rules>

实例

  • Main()
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
//using Microsoft.Extensions.Logging.Console;
using NLog.Extensions.Logging;
using SystemServices;
using 日志系统;

ServiceCollection services = new ServiceCollection();
services.AddLogging(loggingBuilder =>
{
    
    
    //loggingBuilder.AddConsole();
    //loggingBuilder.AddEventLog();  //将日志记录到Windows日志事件中
    loggingBuilder.AddNLog();        //日志记录到文本文件
    //loggingBuilder.SetMinimumLevel(LogLevel.Trace);  //设置最低输出级别信息

});

services.AddScoped<Test1>();
services.AddScoped<Test2>();

using(var sp = services.BuildServiceProvider())
{
    
    
    Test1 test1 = sp.GetRequiredService<Test1>();
    test1.Test();

    Test2 test2 = sp.GetRequiredService<Test2>();
    test2.Test();
}
  • nlog.config
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      autoReload="true"
      internalLogLevel="Info"
      internalLogFile="internal-nlog-AspNetCore.txt">

	<!-- enable asp.net core layout renderers -->
	<extensions>
		<add assembly="NLog.Web.AspNetCore"/>
	</extensions>

	<!-- the targets to write to -->
	<targets>
		<!-- File Target for all log messages with basic details -->
		<target xsi:type="File" archiveAboveSize="100" maxArchiveFiles="10"  name="allfile" fileName="logs/nlog-AspNetCore-all-${shortdate}.log"
				layout="${longdate}|${event-properties:item=EventId_Id:whenEmpty=0}|
				${level:uppercase=true}|${logger}|${message} ${exception:format=tostring}"  />

		<!-- File Target for own log messages with extra web details using some ASP.NET core renderers -->
		<target xsi:type="File" name="ownFile-web" archiveAboveSize="1000" fileName="logs/nlog-AspNetCore-own-${shortdate}.log"
				layout="${longdate}|${event-properties:item=EventId_Id:whenEmpty=0}|
				${level:uppercase=true}|${logger}|${message} ${exception:format=tostring}|url: ${aspnet-request-url}|action: ${aspnet-mvc-action}" />

		<!--Console Target for hosting lifetime messages to improve Docker / Visual Studio startup detection -->
		<target xsi:type="Console" name="lifetimeConsole" layout="${MicrosoftConsoleLayout}" />
	</targets>

	<!-- rules to map from logger name to target -->
	<rules>
		<!--All logs, including from Microsoft-->
		<logger name="*" minlevel="Trace" writeTo="allfile" />

		<logger name="日志系统.*" minlevel="Debug"  writeTo="lifetimeConsole" />

		<logger name="SystemServices.*" minlevel="Warn" maxlevel="Warn" writeTo="lifetimeConsole" />

		<!--Output hosting lifetime messages to console target for faster startup detection -->
		<logger name="Microsoft.Hosting.Lifetime" minlevel="Info" writeTo="lifetimeConsole, ownFile-web" final="true" />

		<!--Skip non-critical Microsoft logs and so log only own logs (BlackHole) -->
		<logger name="Microsoft.*" maxlevel="Info" final="true" />
		<logger name="System.Net.Http.*" maxlevel="Info" final="true" />

		<logger name="*" minlevel="Trace" writeTo="ownFile-web" />
	</rules>
</nlog>
  • 两个Test类

    //------------------Test1------------------
    using Microsoft.Extensions.Logging;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace 日志系统
    {
          
          
        internal class Test1
        {
          
          
            private readonly ILogger<Test1> logger;
            public Test1(ILogger<Test1> logger)
            {
          
          
                this.logger = logger;
            }
    
            public void Test()
            {
          
           
                logger.LogDebug("Test1开始执行");
                logger.LogWarning("Test1程序警告");
                logger.LogError("Test1程序失败");            
            }
        }
    }
    
    
    //------------------Test2------------------
    using Microsoft.Extensions.Logging;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using 日志系统;
    
    namespace SystemServices
    {
          
          
        internal class Test2
        {
          
          
            private readonly ILogger<Test2> logger;
            public Test2(ILogger<Test2> logger)
            {
          
          
                this.logger = logger;
            }
    
            public void Test()
            {
          
          
                logger.LogDebug("Test2开始执行");
                logger.LogWarning("Test2开始执行程序警告");
                logger.LogError("Test2开始执行程序失败");
            }
        }
    }
    

SeriLog: 结构化日志

以键值对的形式传保存日志

主要代码和Nlog基本一致,只要把 SeriLog添加到容器中就好

using Serilog;
using Serilog.Formatting.Json;

ServiceCollection services = new ServiceCollection();
services.AddLogging(loggingBuilder =>
{
    
    
    //loggingBuilder.AddConsole();
    //loggingBuilder.AddEventLog();  //将日志记录到Windows日志事件中
    //loggingBuilder.AddNLog();        //日志记录到文本文件
    //loggingBuilder.SetMinimumLevel(LogLevel.Trace);  //设置最低输出级别信息

    //使用SeriLog
    Serilog.Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Debug()
    .WriteTo.Console(new JsonFormatter())
    .WriteTo.File(new JsonFormatter(), "logs/SeriLog.log")
    .CreateLogger();
    loggingBuilder.AddSerilog();

});

Entity Framework Core

通过C#代码创建表

假设我们要设计这样一张表,我们是不是得一句一句的去写sql语句

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

操作步骤:

  • 先建实体类, 再建实体配置类, 再建数据库配置

  • 控制台迁移数据: Add-Migration Init

  • 将数据写入数据库: update-database

    // 1.建立实体类
    internal class Person
        {
          
          
            public int Id {
          
           get; set; }
            public string Name {
          
           get; set; }
            public int Age {
          
           get; set; }
            public DateTime Birthday {
          
           get; set;}
            public string BirthPlace {
          
           get; set; }
        }
    
    //2.配置实体类
    internal class PersonEntityConfig:IEntityTypeConfiguration<Person>
        {
          
          
            public void Configure(EntityTypeBuilder<Person> builder)  //Configure继承自IEntityTypeConfiguration
            {
          
          
                builder.ToTable("T_Persons");
            }
        }
    
    //3.也可以只定义实体类,但不配置,系统会默认配置
    internal class Dog
        {
          
          
            public int Id {
          
           get; set; }
            public string Name {
          
           get; set; }
        }
    
    //4.写个数据库配置类
      将要写入数据库的类用DbSet<Type>在这里声明,同时该类继承自DbContext.类里重写两个方法,OnConfiguring用啦配置数据的连接
       OnModelCreating组装数据模型
    internal class MyDbContext:DbContext
    {
          
          
        public DbSet<Book> Books {
          
           get; set; }
        public DbSet<Person> Persons {
          
           get; set; }
        public DbSet<Dog> Dog {
          
           get; set; }
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
          
          
            string connStr = "Server=.;Database=demo1;Trusted_Connection=True;Encrypt=false;";
            optionsBuilder.UseSqlServer(connStr);
            optionsBuilder.LogTo(Console.WriteLine);
        }
    
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
          
          
            base.OnModelCreating(modelBuilder);
            modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly);
        }
    }
    
    //5. 控制台迁移数据: Add-Migration Init;  将数据写入数据库: update-database
    

通过C#对表进行怎删改查

上面的内容是对数据库的表进行设计, 怎么实现对设计好的表进行增删改查

插入数据/查询数据

-------------------------main()-----------------------------

using EF_Core;

using (MyDbContext ctx = new MyDbContext())
{
    
    
    //ctx相当于逻辑上的数据库
    Dog d = new Dog();
    d.Name = "旺财";
    ctx.Dog.Add(d);   //把d对象加入Dog这个逻辑上的表里
     //ctx.SaveChanges();
     await ctx.SaveChangesAsync();
    Book b1 = new Book {
    
     Name = "三体", AuthorName = "刘慈欣", Price = 15 };
    Book b2 = new Book {
    
     Name = "西游记", AuthorName = "罗贯中", Price = 20 };
    Book b3 = new Book {
    
     Name = "水浒传", AuthorName = "施耐庵", Price = 18 };
    Book b4 = new Book {
    
     Name = "红楼梦", AuthorName = "曹雪芹", Price = 19 };
    ctx.Books.Add(b1);
    ctx.Books.Add(b2);
    ctx.Books.Add(b3);
    ctx.Books.Add(b4);
    
   var books = ctx.Books.Select(b => b.Price ==15).ToList();  //查询数据
    
    IQueryable<Book> books = ctx.Books.Where(b => b.Price > 18);
    foreach (Book book in books)
    {
    
    
        Console.WriteLine(book.Name);
        Console.WriteLine("**********");
    }
}

删除数据/更新数据

    //删除内容: 1.先查出实体 2.从内存中删除数据
    var b = ctx.Books.Where(b=>b.Name == "西游记");
    foreach(var i in b)
    {
    
    
        //ctx.Books.Remove(i);          //删除数据
        i.AuthorName = "Guanzhong Luo"; //跟新数据
    }

FluenAPI和DataAnnotation区别

FluenAPI复杂不耦合, DataAnnotation简单但是存在耦合

# FluenAPI: 单独写个配置类 
    internal class PersonEntityConfig:IEntityTypeConfiguration<Person>
    {
    
    
        public void Configure(EntityTypeBuilder<Person> builder)
        {
    
    
            builder.ToTable("T_Persons");
        }
    }

# DataAnnotation: 直接通过注解的方式配置
   [Table("T_Cat")]
   internal class Cat
   {
    
    
       public int Id {
    
     get; set; }

       [Required]
       [MaxLength(69)]
       public string Name {
    
     get; set; }
       public int CatId1 {
    
     get; set; }
        [NotMapped]  //不将该属性映射到数据库,尽量不要用
        public int CatId2 {
    
      get; set; }
   }

Guid用法

---------------实体类---------------------
internal class Rabbit
    {
    
    
        public Guid Id {
    
     get; set; }
        public string Name {
    
     get; set; }
    }



---------------main()---------------------
    Rabbit  r1 = new Rabbit();
    r1.Name = "红兔"; 
    ctx.Rabbits.Add(r1);

    await ctx.SaveChangesAsync();

Hi/Lo算法

Migration的常用命令

  • 把数据库升级或者回滚到某个状态: update-database migrationState

  • 删除最后一次的迁移脚本: remov-migration

  • 生成SQL迁移脚本: script-migration

  • add-migration 取一个名字

  • updat-database

反向工程

Scaffold-DbContext "Server=.;Database=Student;Trusted_Connection=True;MultipleActiveResultSets=true;Encrypt=false;" Microsoft.EntityFrameworkCore.SqlServer

EF core查询执行sql语句

  • 标准日志: optionsBuilder.UseLoggerFactory(loggerFactory)
  • 简单日志: optionsBuilder.LogTo(Console.WriteLine)
  • ToQueryString()方法: var books = ctx.Books.Where(b => b.Price ==15); string sql = books.ToQueryString();

怎么记录C#代码对数据库的操作步骤,可以有标准日志和简单日志的方法

 --------------BOOk类----------------
internal class Book
{
    
    
    public long Id {
    
     get; set; }
    public string Name {
    
     get; set; }
    public string Title {
    
     get; set; }
    public string AuthorName {
    
     get; set; }
    public float Price {
    
     get; set; }
}
 --------------BOOk配置类------------------
     
using EF_Core;
using static System.Reflection.Metadata.BlobBuilder;

using (MyDbContext ctx = new MyDbContext())
{
    
    
    Book b1 = new Book {
    
     Name = "三体", AuthorName = "刘慈欣", Price = 15 };
    ctx.Books.Add(b1);
    await ctx.SaveChangesAsync();

   var books = ctx.Books.Where(b => b.Price ==15);
   string sql = books.ToQueryString();
   Console.WriteLine(sql);
    
}

 -------------Main()------------------
internal class MyDbContext:DbContext
{
    
    
    private static ILoggerFactory loggerFactory =
LoggerFactory.Create(b => b.AddConsole());
    public DbSet<Book> Books {
    
     get; set; }
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
    
    
        string connStr = "Server=.;Database=Student;Trusted_Connection=True;Encrypt=false;";
        optionsBuilder.UseSqlServer(connStr);
        optionsBuilder.UseLoggerFactory(loggerFactory); //标准日志
        optionsBuilder.LogTo(Console.WriteLine);   //简单日志
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
    
    
        base.OnModelCreating(modelBuilder);
        modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly);
    }
}

EF Core映射Mysql

所有代码都和上面的SQLServer一样,只是在DbContext上修改, 在OnConfiguring上进行配置

string MySqlConneStr = "server=localhost;user=root;password=123456;database=demo1";
//特别注意:此处使用的是:Pomelo.EntityFramework.MySql包.
var serverVersion = new MySqlServerVersion(new Version(8, 0, 34));
optionsBuilder.UseMySql(MySqlConneStr, serverVersion);

对应关系: 关系配置在任何一方

一个Artcle对应多个Comment

四种关系: 弄清楚谁是主语,谁是宾语

one many
Has HasOne HasMany
With WithOne WithMany

一对多: 关系配置在多端

Comment类和配置

internal class Comment
    {
    
    
        public long Id {
    
      get; set; }
        public string? Message {
    
     get; set; }
        public Artcle? Artcle1 {
    
     get; set; }
    }

    internal class CommentConfig : IEntityTypeConfiguration<Comment>
    {
    
    
        public void Configure(EntityTypeBuilder<Comment> builder)
        {
    
    
            builder.ToTable("T_Commnet");
            builder.HasOne(c => c.Artcle1).WithMany(a=>a.Comments);
        }
    }

Artcle类和配置类

internal class Artcle
{
    
    
    public long Id {
    
     get; set; }
    public string? Title {
    
     get; set; }
    public string? Message {
    
     get; set; }

    public List<Comment> comments {
    
     get; set; } = new List<Comment>();
}

internal class ArtcleConfig : IEntityTypeConfiguration<Artcle>
{
    
    

    public void Configure(EntityTypeBuilder<Artcle> builder)
    {
    
    
        builder.ToTable("T_Artcle");
    }
}

MyDbContext

internal class MyDbContext:DbContext
{
    
    
    public DbSet<Artcle> Artcles {
    
     get; set; }
    public DbSet<Comment> Comments {
    
     get; set; }
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
    
    
        string connStr = "Server=.;Database=demo1;Trusted_Connection=True;Encrypt=false;";
        optionsBuilder.UseSqlServer(connStr);
        //optionsBuilder.LogTo(Console.WriteLine);
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
    
    
        base.OnModelCreating(modelBuilder);
        modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly);
    }
}

main()

using EF_Core;
using Microsoft.EntityFrameworkCore;
using 一对多;

using (MyDbContext ctx = new MyDbContext())
{
    
    
    /*
    Artcle artcle = new();
    artcle.Title = "keson is the best";
    artcle.Message = "据说,他是当代扫地僧";

    //Comment c1 = new() { Message = "太牛了" };
    Comment c2 = new() { Message = "也太搞笑了" };

    //artcle.Comments.Add(c1);
    artcle.Comments.Add(c2);
    
    ctx.Artcles.Add(artcle);
    ctx.SaveChanges();
    */

    /*
    //IQueryable<Artcle> artcles = ctx.Artcles.Where(a => a.Id == 1);  //不查询关联表
    IQueryable<Artcle> artcles = ctx.Artcles.Include(a=>a.Comments). Where(a => a.Id == 1);  //关联表也查询出来
    foreach (var artcle in artcles)
    {
        Console.WriteLine(artcle.Id);
        Console.WriteLine(artcle.Title);
        foreach(var c in artcle.Comments)
        {

        }
    }
     */

    /*
    var comments = ctx.Comments.Include(c=>c.Artcle1).Where(c => c.Id == 1);
    foreach(var c in comments)
    {
        Console.WriteLine(c.Id);
        Console.WriteLine(c.Artcle1.Id);
        Console.WriteLine(c.Artcle1.Message);
    }
    */

    
    //上面的取法会取出所有的字段,通过匿名方法可以只获取需要的字段,提高数据库性能
    var a1 = ctx.Artcles.Select(a => new {
    
     a.Id, a.Title }).First();
    Console.WriteLine(a1.Id + a1.Title);
}

一对多:关系配置在一端

所有的代码都一样,只是配置在一端

    internal class ArtcleConfig : IEntityTypeConfiguration<Artcle>
    {
    
    
        public void Configure(EntityTypeBuilder<Artcle> builder)
        {
    
    
            builder.ToTable("T_Artcle");
            builder.HasMany(a => a.Comments).WithOne(c => c.Artcle1);
        }
    }

EFCore性能

EF Core绝大部分的性能超过绝大多数程序员

单向导航

  • withmany()不带参数

  • 有主从表的用一对多

  • 只是基础处关系的用单向导航

internal class User
{
    
    
    public long Id {
    
     get; set; }
    public string Name {
    
     get; set; }
}

internal class UserConfig : IEntityTypeConfiguration<User>
{
    
    
    public void Configure(EntityTypeBuilder<User> builder)
    {
    
    
        builder.ToTable("T_User");
    }
}

*******************************
    
    internal class Leave
    {
    
    
        public long Id {
    
     get; set; }
        public User Requester {
    
     get; set; }
        public User? Approver {
    
     get; set; }
        public string? remake {
    
     get; set; }
    }

internal class LeaveConfig : IEntityTypeConfiguration<Leave>
{
    
    
    public void Configure(EntityTypeBuilder<Leave> builder)
    {
    
    
        builder.ToTable("T_Leaves");
        builder.HasOne<User>(l => l.Requester).WithMany();
        builder.HasOne<User>(l => l.Approver).WithMany();
    }
}

*******************************
    internal class MyDbContext:DbContext
{
    
    

    public DbSet<User> Users {
    
     get; set; }

    public DbSet<Leave> Leaves {
    
     get; set; }
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
    
    
        string connStr = "Server=.;Database=demo1;Trusted_Connection=True;Encrypt=false;";
        optionsBuilder.UseSqlServer(connStr);
        optionsBuilder.LogTo(Console.WriteLine);
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
    
    
        base.OnModelCreating(modelBuilder);
        modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly);
    }
}

--------------------main()----------------------
    using EF_Core;
using 单向导航;

Console.WriteLine("Hello, World!");

using (MyDbContext ctx = new MyDbContext())
{
    
    
    /*
    var l = ctx.Leaves.FirstOrDefault();
    if (l != null)
    {
        Console.WriteLine(l);
    }
    */

    User u1 = new User {
    
     Name = "孙悟空" };
    Leave leave = new Leave {
    
     Requester = u1, remake = "回家结婚" };

    ctx.Leaves.Add(leave);
    ctx.SaveChanges();
}

猜你喜欢

转载自blog.csdn.net/weixin_45589116/article/details/133363921