Notas de estudo em C# – uso de estudo pessoal<2>



Capítulo 2 A parte básica difícil


Seção 1 Delegação

O que é delegação?

  • Delegado é uma versão atualizada do ponteiro de função
  • Delegar significa que há uma coisa que eu mesmo não faço, mas deixo para os outros fazerem, ou seja, faço indiretamente;
#include <studio.h>

int Add(int a, int b)
{
    
    
	int result = a + b;
	return result
}

int Sub(int a, int b)
{
    
    
	int result = a - b;
	return result
}

int main()
{
    
    
	int x = 100;
	int y = 200;
	int z = 0;
	
	z = Add(x, y);
	printf("%d+%d=%d", x, y, z);
	
	z = Sub(x, y);
	printf("%d-%d=%d", x, y, z);
	
	system("pause");
	return 0;
}

Podemos ver a saída da seguinte forma:

>> 100+200=300
>> 100-200=-100
>> Press any key to continue ...

Neste exemplo, ela é chamada através do nome da função, que é chamada diretamente.

#include <studio.h>

typedef int (* Calc)(int a, int b); // 函数指针,并且定义为一种类型

int Add(int a, int b)
{
    
    
	int result = a + b;
	return result
}

int Sub(int a, int b)
{
    
    
	int result = a - b;
	return result
}

int main()
{
    
    
	int x = 100;
	int y = 200;
	int z = 0;
	
	Calc funcPoint1 = &Add;
	Calc funcPoint2 = &Sub;	

	z = funcPoint1(x, y);
	printf("%d+%d=%d", x, y, z);
	
	z = funcPoint2(x, y);
	printf("%d-%d=%d", x, y, z);
	
	system("pause");
	return 0;
}

Podemos ver a saída da seguinte forma:

>> 100+200=300
>> 100-200=-100
>> Press any key to continue ...

Você pode ver que os resultados de saída são os mesmos, o que mostra que os efeitos das chamadas indiretas e diretas são os mesmos.Este é o ponteiro de função na linguagem C;

  • Tudo é um endereço
    • Uma variável (dados) é um valor armazenado em uma memória a partir de um determinado endereço;
    • Uma função (algoritmo) é um conjunto de instruções em linguagem de máquina armazenadas em uma seção da memória a partir de um determinado endereço;
  • Chamada direta e chamada indireta
    • Chamada direta: Chama a função através do nome da função, o processador obtém diretamente o endereço da função através do nome da função e inicia a execução -> retorno;
    • Chamada indireta: Chama uma função através de um ponteiro de função. O processador obtém o endereço da função lendo o valor armazenado no ponteiro de função e inicia a execução -> retorno;
  • Não existe nenhuma entidade funcional correspondente à delegação em Java;
  • A delegação é simples e prática
    • Delegado de ação; para tipo Void
    • Delegado de função; usado com parâmetros

Parte 1 Exemplo de ação e delegação de função

namespace ConsoleHelloWorld
{
    
    
    class Program
    {
    
    
        static void Main(string[] args)
        {
    
    
            Calculator calculator = new Calculator();
            Action action = new Action(calculator.Report); // 注意这里没有圆括号,这里只需要方法名,而不是调用方法
            calculator.Report(); // 直接调用
            action.Invoke(); // 间接调用,模仿函数指针的写法
            action(); // 间接调用,简洁的写法

            // 参数,参数,返回类型
            Func<int, int, int> func = new Func<int, int, int>(calculator.Add); 
            Func<int, int, int> func2 = new Func<int, int, int> (calculator.Sub);

            int x = 100;
            int y = 200;
            int z = 0;

            // 间接调用,函数指针式的写法
            z = func.Invoke(x, y);
            Console.WriteLine(z);

            z = func2.Invoke(x, y);
            Console.WriteLine(z);

            // 间接调用,简洁的写法
            z = func(x, y);
            Console.WriteLine(z);

            z = func2(x, y);
            Console.WriteLine(z);
        }
    }

    class Calculator
    {
    
    
        public void Report()
        {
    
    
            Console.WriteLine("I have 3 methods");
        }

        public int Add(int a, int b)
        {
    
     
            int result = a + b;
            return result;
        }

        public int Sub(int a, int b)
        {
    
    
            int result = a - b;
            return result;
        }
    }
}

A execução do programa acima fornecerá a seguinte saída:

I have 3 methods
I have 3 methods
I have 3 methods
300
-100
300
-100

Parte 2 Delegação Personalizada

  • Como o delegado é uma classe, que é um tipo de dados e um tipo de dados de referência, o delegado pode declarar variáveis ​​e instâncias;
  • O método de declaração de um delegado não é o mesmo que o método de declaração de uma classe geral, mas é mais parecido com o método de declaração de um ponteiro de função em C/C++;

O exemplo a seguir é a declaração e o uso de um delegado personalizado;

namespace ConsoleHelloWorld
{
    
    
    public delegate double Calc(double x, double y);
    // delegate 是类,需要声明在名称空间体里面;
    // public 是访问范围,delegate 是告诉编译器要声明一个委托
    // 第一个 double 是目标方法的返回值类型
    // 然后 Calc 是委托的名字
    // 后面的圆括号里面是目标方法的参数列表
    // 到此自定义委托类型声明完成

    class Program
    {
    
    
        static void Main(string[] args)
        {
    
    
            Calculator calculator = new Calculator();
            // 传递的方法的参数列表必须和声明时一样,返回类型也必须一致
            Calc calc1 = new Calc(calculator.Add); 
            Calc calc2 = new Calc(calculator.Sub);
            Calc calc3 = new Calc(calculator.Mul);
            Calc calc4 = new Calc(calculator.Div);

            double a = 100;
            double b = 200;
            double c = 0;

            c = calc1.Invoke(a, b);
            Console.WriteLine(c);

            c = calc2.Invoke(a, b);
            Console.WriteLine(c);

            c = calc3.Invoke(a, b);
            Console.WriteLine(c);

            c = calc4.Invoke(a, b);
            Console.WriteLine(c);
        }
    }

    class Calculator
    {
    
    
        // 有四个方法,除了名字不同,返回值类型和参数列表都是一样的
        public double Add(double x, double y)
        {
    
     
            return x + y;
        }

        public double Sub(double x, double y)
        {
    
    
            return x - y;
        }

        public double Mul(double x, double y)
        {
    
    
            return x * y;
        }

        public double Div(double x, double y)
        {
    
    
            return x / y;
        }
    }
}

Executando o código acima, você pode obter a seguinte saída:
Insira a descrição da imagem aqui

Quando customizamos a delegação, precisamos ficar atentos a alguns pontos:

  • O delegado e o método encapsulado devem permanecer "compatíveis com o tipo"
  • Não o coloque no lugar errado ao declarar o delegado. O delegado é uma classe e precisa ser declarado no corpo do namespace. Se você colocá-lo no lugar errado, ele pode não ser executado ou se tornar uma classe aninhada;

Insira a descrição da imagem aqui
Como você pode ver na imagem acima, a primeira linha é a declaração do delegado, e as quatro linhas seguintes são métodos compatíveis com ela;

Parte 3 Uso geral de delegação

No trabalho, o delegado geralmente é passado como parâmetro para outro método. A vantagem disso é que o método encapsulado pelo delegado pode ser chamado indiretamente, formando uma estrutura dinâmica de chamada de método;

  • O método modelo grava um método que usa o método externo especificado para gerar resultados por meio dos parâmetros delegados passados;
    • Equivalente à pergunta de preencher as lacunas
    • Muitas vezes localizado no meio do código
    • O delegado tem um valor de retorno
    • É equivalente a escrever um método, que é um modelo. Uma parte deste modelo é incerta e outras partes são determinadas. Esta parte incerta é preenchida pelo método contido nos parâmetros do tipo delegado passado;
  • O método de retorno de chamada chama o método externo especificado;
    • Equivalente a uma linha de montagem
    • Geralmente localizado no final do código
    • Os delegados não têm valor de retorno e geralmente são usados ​​para lidar com algum trabalho final;

O seguinte mostra o uso de métodos de modelo:

namespace ConsoleHelloWorld
{
    
    
    class Program
    {
    
    
        static void Main(string[] args)
        {
    
    
            ProductFactory productFactory = new ProductFactory();
            WrapFactory wrapFactory = new WrapFactory();

            Func<Product> func1 = new Func<Product>(productFactory.MakePizza);
            Func<Product> func2 = new Func<Product>(productFactory.MakeToyCar);

            Box box1 = wrapFactory.WrapProduct(func1);
            Box box2 = wrapFactory.WrapProduct(func2);

            Console.WriteLine(box1.Product.Name);
            Console.WriteLine(box2.Product.Name);
        }
    }

    class Product
    {
    
     
        public string Name {
    
     get; set; }    
    }

    class Box
    {
    
     
        public Product Product {
    
     get; set; }    
    }

    class WrapFactory
    {
    
    
        public Box WrapProduct ( Func<Product> getProduct )
        {
    
    
            // 模板方法
            Box box = new Box();
            // 执行传进来的委托所封装的方法,这就是间接调用
            Product product = getProduct.Invoke(); // 获取产品,将产品装入 Box
            box.Product = product; 
            return box;
            // 写成模版方法的好处是,Product类,Box类还有WrapFactory类都不需要在修改,
            // 只需要扩展产品工厂,让其产出更多的产品,不管生产哪种产品的方法,
            // 只需要将该方法封装在委托类型的对象里,传给模版方法,这个模版方法一定可以将
            // 产品包装成箱子返回回来,极大地实现代码的重复使用
        }
    }

    class ProductFactory
    {
    
    
        public Product MakePizza()
        {
    
     
            Product product = new Product();
            product.Name = "Pizza";
            return product;
        }

        public Product MakeToyCar()
        {
    
     
            Product product = new Product();
            product.Name = "Toy Cat";
            return product;
        }
    }
}

O seguinte mostra o uso de métodos de retorno de chamada:

namespace ConsoleHelloWorld
{
    
    
    class Program
    {
    
    
        static void Main(string[] args)
        {
    
    
            ProductFactory productFactory = new ProductFactory();
            WrapFactory wrapFactory = new WrapFactory();

            Func<Product> func1 = new Func<Product>(productFactory.MakePizza);
            Func<Product> func2 = new Func<Product>(productFactory.MakeToyCar);

            Logger logger = new Logger();
            Action<Product> log = new Action<Product>(logger.Log);

            Box box1 = wrapFactory.WrapProduct(func1, log);
            Box box2 = wrapFactory.WrapProduct(func2, log);

            Console.WriteLine(box1.Product.Name);
            Console.WriteLine(box2.Product.Name);
        }
    }

    class Logger
    {
    
    
        public void Log(Product product)
        {
    
    
            // Log 以回调的形式传进模版的方法里
            Console.WriteLine("Product '{0}' created at {1}. Price is {2}.", product.Name, DateTime.UtcNow, product.Price);
        }
    }

    class Product
    {
    
     
        public string Name {
    
     get; set; }
        public double Price {
    
     get; set; }
    }

    class Box
    {
    
     
        public Product Product {
    
     get; set; }    
    }

    class WrapFactory
    {
    
    
        public Box WrapProduct ( Func<Product> getProduct, Action<Product> logCallback)
        {
    
    
            // 模板方法
            Box box = new Box();
            // 执行传进来的委托所封装的方法,这就是间接调用
            Product product = getProduct.Invoke(); // 获取产品,将产品装入 Box
            if (product.Price >= 50)
            {
    
     
                logCallback(product);
            }

            box.Product = product; 
            return box;
            // 写成模版方法的好处是,Product类,Box类还有WrapFactory类都不需要在修改,
            // 只需要扩展产品工厂,让其产出更多的产品,不管生产哪种产品的方法,
            // 只需要将该方法封装在委托类型的对象里,传给模版方法,这个模版方法一定可以将
            // 产品包装成箱子返回回来,极大地实现代码的重复使用
        }
    }

    class ProductFactory
    {
    
    
        public Product MakePizza()
        {
    
     
            Product product = new Product();
            product.Name = "Pizza";
            product.Price = 20;
            return product;
        }

        public Product MakeToyCar()
        {
    
     
            Product product = new Product();
            product.Name = "Toy Cat";
            product.Price = 120;
            return product;
        }
    }
}

Quer seja um método de modelo ou um método de retorno de chamada, um método externo é encapsulado usando parâmetros do tipo delegado e, em seguida, o método é passado para o método para chamadas indiretas. Este é o uso convencional de delegados.
A delegação pode ser muito perigosa se for mal utilizada;

  • Este é um tipo de acoplamento forte no nível do método, e precisamos ser cautelosos no trabalho real;
  • Reduza a legibilidade e aumente a dificuldade de depuração;
  • O entrelaçamento de retornos de chamada de delegados, chamadas assíncronas e multithreading tornará o código difícil de manter e ler, o que é catastrófico;
  • O uso inadequado de delegados pode causar vazamentos de memória e redução do desempenho do programa;

Parte 4 Uso avançado de delegação

Insira a descrição da imagem aqui

Delegação multicast refere-se a uma delegação que encapsula mais de um método. Veja a seguir um exemplo:

namespace ConsoleHelloWorld
{
    
    
    class Program
    {
    
    
        static void Main(string[] args)
        {
    
    
            Student student1 = new Student() {
    
     Id = 1, PenColor = ConsoleColor.Yellow };
            Student student2 = new Student() {
    
     Id = 2, PenColor = ConsoleColor.Green };
            Student student3 = new Student() {
    
     Id = 3, PenColor = ConsoleColor.Red };

            Action action1 = new Action(student1.DoHomework);
            Action action2 = new Action(student2.DoHomework);
            Action action3 = new Action(student3.DoHomework);

            // 多播委托的写法:
            action1 += action2; // 将 aciton2 合并到 action1
            action1 += action3;

            action1.Invoke();
            // 多播委托的执行顺序是按照你封装方法的顺序执行的
        }
    }

    class Student
    {
    
     
        public int Id {
    
     get; set; }
        public ConsoleColor PenColor {
    
     get; set; }

        public void DoHomework()
        {
    
     
            for (int i = 0; i < 5; i++) 
            {
    
    
                Console.ForegroundColor = this.PenColor;
                Console.WriteLine("Student {0} doing homework {1} hours.", this.Id, i);
                Thread.Sleep(1000); // 线程暂停一秒钟
            }
        }
    }
}

delegação multicast
Chamadas assíncronas implícitas

  • Chamada assíncrona: relativa à chamada síncrona,
    • Sincronização: Depois que você terminar, continuarei com base no seu trabalho;
    • Assíncrono: Nós dois fazemos ao mesmo tempo, ou seja, cada um faz a sua coisa;
  • Comparação de chamadas síncronas e chamadas assíncronas
    • Todo programa em execução é um processo
    • Cada processo pode ter um ou mais threads, o primeiro thread é chamado de thread principal e os demais são threads ramificados.
    • Ao chamar métodos no mesmo thread, este último só pode ser executado após a execução do anterior, o que é chamado de chamada síncrona;
    • O mecanismo subjacente das chamadas assíncronas éMultithreading
    • As chamadas síncronas são chamadas seriais de thread único e as chamadas assíncronas são chamadas paralelas multithread;

A seguir está um exemplo de uma chamada assíncrona para uma chamada síncrona

namespace ConsoleHelloWorld
{
    
    
    class Program
    {
    
    
        static void Main(string[] args)
        {
    
    
            Student student1 = new Student() {
    
     Id = 1, PenColor = ConsoleColor.Yellow };
            Student student2 = new Student() {
    
     Id = 2, PenColor = ConsoleColor.Green };
            Student student3 = new Student() {
    
     Id = 3, PenColor = ConsoleColor.Red };

            // 直接同步调用
            student1.DoHomework();
            student2.DoHomework();
            student3.DoHomework();

            Console.WriteLine("=============================================");

            Action action1 = new Action(student1.DoHomework);
            Action action2 = new Action(student2.DoHomework);
            Action action3 = new Action(student3.DoHomework);

            // 使用委托的隐式异步调用
            action1.BeginInvoke(null, null);
            action2.BeginInvoke(null, null);
            action3.BeginInvoke(null, null);

            Console.WriteLine("=============================================");

            // 使用委托的显式异步调用
            Task task1 = new Task(new Action(student1.DoHomework));
            Task task2 = new Task(new Action(student2.DoHomework));
            Task task3 = new Task(new Action(student3.DoHomework));

            task1.Start();
            task2.Start();
            task3.Start();

            Console.WriteLine("=============================================");

            // 单播委托的间接同步调用
            action1.Invoke();
            action2.Invoke();
            action3.Invoke();

            Console.WriteLine("=============================================");

            // 多播委托的间接同步调用
            action1 += action2;
            action2 += action3;
            action1();

            Console.WriteLine("=============================================");
        }
    }

    class Student
    {
    
     
        public int Id {
    
     get; set; }
        public ConsoleColor PenColor {
    
     get; set; }

        public void DoHomework()
        {
    
     
            for (int i = 0; i < 5; i++) 
            {
    
    
                Console.ForegroundColor = this.PenColor;
                Console.WriteLine("Student {0} doing homework {1} hours.", this.Id, i);
                Thread.Sleep(1000); // 线程暂停一秒钟
            }
        }
    }
}

Parte 5 Use interface quando apropriado para substituir alguns usos de delegação

O uso inadequado da delegação aumentará a dificuldade de manutenção do código.O uso de interfaces pode evitar esses problemas desnecessários e obter as mesmas funções;

namespace ConsoleHelloWorld
{
    
    
    class Program
    {
    
    
        static void Main(string[] args)
        {
    
    
            IProductFactory pizzaFactory = new PizzaFactory();
            IProductFactory toycarFactory = new ToyFactory();
            WrapFactory wrapFactory = new WrapFactory();

            Logger logger = new Logger();
            Action<Product> log = new Action<Product>(logger.Log);

            Box box1 = wrapFactory.WrapProduct(pizzaFactory, log);
            Box box2 = wrapFactory.WrapProduct(toycarFactory, log);

            Console.WriteLine(box1.Product.Name);
            Console.WriteLine(box2.Product.Name);
        }
    }

    interface IProductFactory
    {
    
    
        Product Make();
    }

    class PizzaFactory : IProductFactory // 这个类实现了IProductFactory的接口
    {
    
    
        public Product Make()
        {
    
    
            // 重构是指基本不改变原来的代码,只是把代码放到更合适的地方去
            Product product = new Product();
            product.Name = "Pizza";
            product.Price = 20;
            return product;
        }
    }

    class ToyFactory : IProductFactory
    {
    
    
        public Product Make()
        {
    
    
            Product product = new Product();
            product.Name = "Toy Cat";
            product.Price = 120;
            return product;
        }
    }

    class Logger
    {
    
    
        public void Log(Product product)
        {
    
    
            // Log 以回调的形式传进模版的方法里
            Console.WriteLine("Product '{0}' created at {1}. Price is {2}.", product.Name, DateTime.UtcNow, product.Price);
        }
    }

    class Product
    {
    
    
        public string Name {
    
     get; set; }
        public double Price {
    
     get; set; }
    }

    class Box
    {
    
    
        public Product Product {
    
     get; set; }
    }

    class WrapFactory
    {
    
    
        public Box WrapProduct(IProductFactory productFactory, Action<Product> logCallback)
        {
    
    
            // 模板方法
            Box box = new Box();
            Product product = productFactory.Make(); 
            if (product.Price >= 50)
            {
    
    
                logCallback(product);
            }
            box.Product = product;
            return box;
        }
    }
}

Pode-se observar que após a refatoração e o uso de interfaces, não há delegação no programa e não há acoplamento no nível do método;
este exemplo mostra que as interfaces podem ser usadas para substituir a delegação;


Seção 2 Incidente

Parte 1 Compreensão preliminar do evento

  • Definição: Evento, traduzido como "evento"
    • Algo que pode acontecer, principalmente algo mais importante;
    • algo que acontece, especialmente algo importante.
    • A explicação suave é “o que pode acontecer”, que é chamado de evento;
  • Função: permite que um objeto ou classe tenhacapacidades de notificaçãoum membro de
    • Na linguagem C#, um evento é um membro de um tipo que permite que um objeto ou classe forneça notificações
    • Um evento é um membro que permite que um objeto ou classe forneça notificações.
    • O objeto A possui um tempo B, o que significa que quando B ocorre, A tem a capacidade de notificar outros objetos;
    • As mensagens enviadas através do evento e relacionadas ao próprio evento são chamadas de parâmetros de evento EventArgs
    • O comportamento de executar ações com base em parâmetros simultâneos e de eventos é chamado de tempo de resposta ou processamento de eventos. O que é feito ao processar um evento é chamado de manipulador de eventos.
  • Uso: usado para coordenação de ações e transferência de informações (envio de mensagens) entre objetos ou classes
    • A função do evento = notificar outros objetos ou classes + parâmetros opcionais do evento (ou seja, informações detalhadas)
  • Princípio: Dois "5" no modelo de evento (também chamado de modelo ocorrência-resposta)
    • Cinco partes em "Ocorrência->Resposta": você levanta quando o despertador toca, você cozinha quando a criança está com fome... Existe uma relação implícita de "assinatura";
    • Cinco ações em "Ocorrência->Resposta":
      • (1) tenho um evento;
      • (2) Uma pessoa ou grupo de pessoas se preocupa com o meu incidente;
      • (3) Este meu incidente aconteceu;
      • (4) As pessoas preocupadas com este incidente serão notificadas imediatamente;
      • (5) A pessoa que foi notificada responde ao evento (também denominado “processamento do evento”) com base nas informações do evento recebidas (também denominadas “dados de tempo”, “parâmetros do evento”, “notificação”);
  • A terminologia relevante precisa ser definida para facilitar a comunicação e a aprendizagem
    • Inscritos no evento, são iguais ao receptor da mensagem do evento, ao respondedor do horário, ao manipulador do evento e ao objeto notificado pelo evento, o que facilita a comunicação.Use apenas assinantes de eventos
    • parâmetros de evento, que é igual a informações de evento, mensagem de evento e dados de evento e é fácil de comunicar.Use apenas parâmetros de evento
  • dica
    • Os eventos são usados ​​principalmente na programação do lado do cliente desenvolvida para desktops, telefones celulares, etc., porque esses programas clientes são frequentemente "conduzidos" pelos usuários por meio de eventos;
    • O modelo de evento é abstraído do mundo real e várias linguagens de programação implementam esse mecanismo de diferentes maneiras;
    • Não há membro de evento na linguagem Java, nem qualquer tipo de dados, como delegação. Os eventos Java são implementados usando interfaces;
    • O modelo de evento é uma coisa boa, mas tem falhas. Se não houver restrições ao escrever, a lógica do programa será facilmente confundida. Após um longo período de resumo, resumi padrões de arquitetura como MVC, MVP e MVVM.Estes são padrões de eventos mais avançados e mais avançados.uso válido;
    • Durante o desenvolvimento diário, há mais oportunidades de usar eventos existentes e menos oportunidades de declarar eventos você mesmo;

Parte 2 Inscrição em Evento

  • Cinco componentes do modelo de evento
    • O proprietário do evento, fonte do evento, objeto, o evento é acionado internamente pelo proprietário;
    • Membros do evento, ou seja, o próprio evento, evento, membros
    • O respondedor do evento, ou seja, o assinante do evento, o objeto.Quando o evento ocorre, quais objetos são notificados são os respondedores do evento;
    • Manipulador de eventos, membro, é essencialmente um método de retorno de chamada
    • A assinatura de eventos, que associa processadores de eventos a eventos, é essencialmente um “contrato” baseado no tipo de delegação.
  • Perceber
    • Manipuladores de eventos são membros do método
    • Ao conectar um manipulador de eventos, você pode usar uma instância delegada ou usar diretamente o nome do método.Este é o açúcar de sintaxe;
    • A assinatura do manipulador de eventos para o evento não é arbitrária e é verificada pela correspondência do tipo de delegado usado ao declarar o evento;
    • Os eventos podem ser chamados de forma síncrona ou assíncrona;

Aqui está um pequeno exemplo

namespace ConsoleHelloWorld
{
    
    
    class Program
    {
    
    
        static void Main(string[] args)
        {
    
    
            System.Timers.Timer timer = new System.Timers.Timer(); // 事件拥有者 timer
            timer.Interval = 1000; // ms
            Boy boy = new Boy(); // 事件的响应者是 boy 对象
            Girl girl = new Girl();

            timer.Elapsed += boy.Action;// += 是订阅的写法,后面要跟上事件响应者的事件处理器
            timer.Elapsed += girl.Action;
            // 事件 Elapsed,事件订阅 += 
            timer.Start();
            Console.ReadLine();
        }
    }

    class Boy
    {
    
    
        // 事件的处理器
        internal void Action(object sender, ElapsedEventArgs e)
        {
    
    
            Console.WriteLine("Jump!");
        }
    }

    class Girl
    {
    
    
        internal void Action(object sender, ElapsedEventArgs e)
        {
    
    
            Console.WriteLine("Sing!");
        }
    }
}

A imagem acima mostra como é quando um evento tem dois manipuladores de eventos ao mesmo tempo;


Insira a descrição da imagem aqui
A imagem acima mostra o modelo de mecanismo de evento padrão, que possui uma estrutura clara e é o protótipo de padrões de design como MVC e MVP. O programa
a seguir é uma explicação desse modelo de mecanismo de evento padrão.

namespace WindowsFormsApp1
{
    
    
    internal static class Program
    {
    
    
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
    
    
            Form form = new Form(); // 事件的拥有者 form
            Controller controller = new Controller(form); // 事件的响应者 controller
            form.ShowDialog();
        }
    }

    class Controller
    {
    
    
        private Form form;
        public Controller(Form form)
        {
    
    
            if (form != null)
            {
    
    
                this.form = form;
                this.form.Click += this.FormClicked; // 事件是 form 的 click,+=实现事件订阅
            }
        }

        // 事件处理器
        private void FormClicked(object sender, EventArgs e)
        {
    
    
            this.form.Text = DateTime.Now.ToString();
        }
    }
}

Insira a descrição da imagem aqui


Insira a descrição da imagem aqui
A imagem acima mostra que o objeto usa seu próprio método para assinar e processar seus próprios eventos; o
programa a seguir é uma explicação da imagem acima e também aborda qual derivação é

namespace WindowsFormsApp1
{
    
    
    internal static class Program
    {
    
    
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
    
    
            MyForm form = new MyForm(); // 事件的拥有者 form,事件的响应者也是 from
            form.Click += form.FormClicked; // 事件是 Click,事件的订阅是 +=
            form.ShowDialog();
        }
    }

    class MyForm : Form // 派生,集成原有的方法之外还可以添加新的方法
    {
    
    
        // 事件处理器
        internal void FormClicked(object sender, EventArgs e)
        {
    
    
            this.Text = DateTime.Now.ToString();
        }
    }
}

Insira a descrição da imagem aqui


Insira a descrição da imagem aqui
A figura acima mostra o mais comumente usado. A característica é que o proprietário do evento é um membro do campo do respondente do evento. É a assinatura de evento padrão e a estrutura de processamento no Windows. O programa a seguir é uma explicação do exemplo na figura acima
.

namespace WindowsFormsApp1
{
    
    
    internal static class Program
    {
    
    
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
    
    
            MyForm form = new MyForm();
            form.ShowDialog();
        }
    }

    // 事件的响应者是 MyForm 的对象
    class MyForm : Form
    {
    
    
        private TextBox textBox;
        private Button button; // button 是事件的拥有者,且为字段成员

        public MyForm()
        {
    
    
            this.textBox = new TextBox();
            this.button = new Button();

            // 显示在 form 当中
            this.Controls.Add(this.textBox);
            this.Controls.Add(this.button);

            this.button.Click += this.ButtonClicked; // 事件是 Click
            // += 是事件的订阅

            this.button.Text = "Say Hello!";
            this.button.Top = 100;
        }

        // 事件的处理器
        private void ButtonClicked(object sender, EventArgs e)
        {
    
    
            this.textBox.Text = "Hello World";
        }
    }
}

Insira a descrição da imagem aqui

Parte 3 Declaração de Eventos

Método completo de declaração de eventoExemplo

namespace ConsoleHelloWorld
{
    
    
    class Program
    {
    
    
        static void Main(string[] args)
        {
    
    
            Customer customer = new Customer(); // 事件的拥有者
            Waiter waiter = new Waiter(); // 事件的响应者
            customer.Order += waiter.Action; // 使用 Action 的方法作为 waiter 类型的事件处理器
            // Order 事件 += 事件的订阅

            customer.Action();

            customer.PayTheBill();
        }
    }

    public class OrderEventArgs : EventArgs
    {
    
    
        public string DishName {
    
     get; set; }
        public string Size {
    
     get; set; }
    }

    public delegate void OrderEventHandler(Customer customer, OrderEventArgs e);

    
    public class Customer // 需要保证访问级别是一致的
    {
    
     
        private OrderEventHandler orderEventHandler;

        // 事件 Order
        public event OrderEventHandler Order 
        {
    
    
            add 
            {
    
    
                this.orderEventHandler += value;
            }

            remove
            {
    
    
                this.orderEventHandler -= value;
            }
        }

        public double Bill {
    
     get; set; }
        public void PayTheBill()
        {
    
    
            Console.WriteLine("I will pay ${0}", this.Bill);
        }

        public void WalkIn()
        {
    
    
            Console.WriteLine("Walk into the restaurant.");
        }

        public void SitDown()
        {
    
    
            Console.WriteLine("Sit Dowm.");
        }

        public void Think()
        {
    
    
            for (int i = 0; i < 5; i++)
            {
    
    
                Console.WriteLine("Let me think.......");
                Thread.Sleep(1000);
            }

            if (this.orderEventHandler != null)
            {
    
     
                OrderEventArgs e = new OrderEventArgs();
                e.DishName = "Kongpao Chicken";
                e.Size = "large";
                this.orderEventHandler.Invoke(this, e);
            }
        }

        public void Action()
        {
    
    
            Console.ReadLine();
            this.WalkIn(); ;
            this.SitDown();
            this.Think();
        }
    }

    // 事件的响应者
    public class Waiter
    {
    
    
        internal void Action(Customer customer, OrderEventArgs e)
        {
    
    
            Console.WriteLine("I will serve you the dish - {0}", e.DishName);
            double price = 10;

            switch (e.Size)
            {
    
    
                case "small":
                    price = price * 0.5;
                    break;
                case "large":
                    price = price * 1.5;
                    break;
                default:
                    break;
            }
            customer.Bill += price;
        }
    }
}

Método simples de declaração de eventoExemplo

namespace ConsoleHelloWorld
{
    
    
    class Program
    {
    
    
        static void Main(string[] args)
        {
    
    
            Customer customer = new Customer(); // 事件的拥有者
            Waiter waiter = new Waiter(); // 事件的响应者
            customer.Order += waiter.Action; // 使用 Action 的方法作为 waiter 类型的事件处理器
            // Order 事件 += 事件的订阅

            customer.Action();

            customer.PayTheBill();
        }
    }

    public class OrderEventArgs : EventArgs
    {
    
    
        public string DishName {
    
     get; set; }
        public string Size {
    
     get; set; }
    }

    public delegate void OrderEventHandler(Customer customer, OrderEventArgs e);

    
    public class Customer // 需要保证访问级别是一致的
    {
    
    

        public event OrderEventHandler Order;

        public double Bill {
    
     get; set; }
        public void PayTheBill()
        {
    
    
            Console.WriteLine("I will pay ${0}", this.Bill);
        }

        public void WalkIn()
        {
    
    
            Console.WriteLine("Walk into the restaurant.");
        }

        public void SitDown()
        {
    
    
            Console.WriteLine("Sit Dowm.");
        }

        public void Think()
        {
    
    
            for (int i = 0; i < 5; i++)
            {
    
    
                Console.WriteLine("Let me think.......");
                Thread.Sleep(1000);
            }

            if (this.Order != null)
            {
    
     
                OrderEventArgs e = new OrderEventArgs();
                e.DishName = "Kongpao Chicken";
                e.Size = "large";
                this.Order.Invoke(this, e);
            }
        }

        public void Action()
        {
    
    
            Console.ReadLine();
            this.WalkIn(); ;
            this.SitDown();
            this.Think();
        }
    }

    // 事件的响应者
    public class Waiter
    {
    
    
        internal void Action(Customer customer, OrderEventArgs e)
        {
    
    
            Console.WriteLine("I will serve you the dish - {0}", e.DishName);
            double price = 10;

            switch (e.Size)
            {
    
    
                case "small":
                    price = price * 0.5;
                    break;
                case "large":
                    price = price * 1.5;
                    break;
                default:
                    break;
            }

            customer.Bill += price;
        }
    }
}

Por que precisamos de eventos quando temos campos do tipo delegado?

  • Os membros do evento podem tornar o relacionamento entre a lógica do programa e os objetos mais razoável e seguro;

Parte 4 Esclarecimento

Insira a descrição da imagem aqui


Categoria da seção 3

A programação orientada a objetos possui três características principais: encapsulamento, herança e polimorfismo;
o que é uma classe

  • É uma estrutura de dados estrutura de dados
  • é um tipo de dados
namespace ConsoleHelloWorld {
    
    
    class Program {
    
    
        static void Main(string[] args) {
    
    
            Student student = new Student(id: 1, name: "Tim"); // 创建实例
            student.Report();
        }
    }

    class Student {
    
    
        public Student(int id, string name) {
    
    
            this.ID = id; ;
            this.Name = name;
        }

        // 析构器,手动释放资源
        // 当程序关闭之前,变量 student 没人引用,系统会自动调用析构器
        ~ Student() {
    
    
            Console.WriteLine("Bye bye, Release the system resources");
        }
        public int ID {
    
     get; set; }
        public String Name {
    
     get; set; }
        public void Report() {
    
    
            Console.WriteLine($"I'm #{
      
      this.ID} student, my name is {
      
      this.Name}.");
        }
    }
}

Declaração de classe da parte 1

  • As aulas podem ser declaradas nos três locais a seguir:
    • Dentro de um namespace (mais comum)
    • Fora do namespace explícito (na verdade declarado no namespace global)
    • Dentro do corpo da classe (classes membros, ou classes aninhadas, também são usadas com mais frequência)

Declaração, definição é Definição, que pode ser misturada e compreendida em C# e Java, mas em C++ as duas não são a mesma coisa. Em C++, a declaração de uma classe é separada da definição da classe. Se você deseja aplicar uma classe em C++, você precisa referenciar o arquivo de cabeçalho, mas em C# e Java, isso não é necessário; portanto, no linguagem C#
,Declaração é definição
Insira a descrição da imagem aqui
O nome da classe e o corpo da classe na nomenclatura da classe são indispensáveis

  • Os modificadores de classe são opcionais:

    • novo
    • público
    • protegido
    • interno
    • privado
    • abstrato
    • selado
    • estático
  • O novo modificador é adequado para classes aninhadas. Ele especifica que a classe oculta membros herdados com o mesmo nome. Se o novo modificador for usado em uma declaração de classe que não seja uma declaração de classe aninhada, causará um erro em tempo de compilação;

  • Os modificadores públicos, protegidos, internos e privados controlarão a acessibilidade da classe.Dependendo do contexto em que a classe é declarada,alguns destes modificadores podem não ser permitidos;

  • abstrato, selado e estático são sobre herança;

  • A classe pública é acessível a outros projetos, ou seja, outros Assemblies podem acessar as classes deste Assembléia;

  • As classes internas podem ser acessadas livremente no mesmo projeto, o que significa que o nível de acesso é limitado ao projeto, ou seja, Assembly;

  • classe privada só pode ser vista se esta classe for membro de outras classes

Parte 2 Herança de Classe

É muito importante para a programação orientada a objetos. Seus recursos mais significativos são encapsulamento, herança e polimorfismo, e o polimorfismo é baseado em herança;

A sintaxe para declarar uma classe herdada é relativamente simples:

class + 类名 : 类基础 + 类体

De modo geral, a classe base e a classe derivada são um par, e a classe pai e a subclasse são um par;

namespace ConsoleHelloWorld {
    
    
    class Program {
    
    
        static void Main(string[] args) {
    
    
            Type type = typeof(Car);
            Type baseType = type.BaseType;
            Console.WriteLine(baseType.FullName);
            // 可以看到输出 >> ConsoleHelloWorld.Vehicle
            // 说明 Car 的基类是 Vehicle
            // "是一个 is a" 概念:
            // 一个派生类的实例,也是基类的实例,但反过来不正确
            // 也就是 Car 的实例也是 Vehicle 的实例
            // 也就是:一个汽车,也是一个交通工具
            Car car = new Car(); // car 是实例,引用着 new 操作符创建的实例
            Console.WriteLine(car is Vehicle);
            // 可以看到输出 >> True
            Vehicle vehicle = new Vehicle();
            Console.WriteLine(vehicle is Car);
            // 可以看到输出 >> False

            // 从 "是一个 is a" 概念,引出 "可以用一个父类类型的变量,引用子类类型的实例"
            // 在多态中是非常有用的,下面是引出概念的示例:
            Vehicle vehicle1 = new Car();
            Object o1 = new Vehicle();
            Object o2 = new Car();
        }
    }

    // 作为基类
    // 如果声明一个类没有说基类是谁
    // 下面的例子编译器等效 class Vehicle : Object { }
    class Vehicle {
    
     
    
    }

    // 由 Vehicle 派生而来,Car 继承了 Vehicle
    class Car : Vehicle {
    
     
        
    }
}

requer atenção:
Se seal for usado para modificar uma classe, essa classe não poderá ser usada como classe base.
Uma classe em C# só pode ter no máximo uma classe base, mas pode ter várias interfaces base.

  • Padronize a terminologia:
    • Herdar/derivar de uma classe base;
    • Uma determinada classe implementa uma determinada interface base;

O nível de acesso de uma subclasse não pode exceder o nível de acesso da classe pai

Parte 3 Herança e acesso de membros

A essência da herança: consiste na expansão horizontal e vertical da classe derivada nos membros existentes da classe base.

  • A classe derivada é baseada nos membros existentes da classe base: quando ocorre a herança, a subclasse obtém todos os membros da classe pai. Quaisquer que sejam os membros da classe pai, a subclasse obtém todos eles;
  • No processo de derivação e herança, a expansão é realizada: os membros da classe só podem tornar-se cada vez mais, mas não podem tornar-se cada vez menos;
  • Os dois pontos acima levam a: uma vez que um membro da classe é introduzido na cadeia de herança, ele será transmitido o tempo todo e não poderá ser removido da cadeia de herança; esta é uma característica das linguagens estáticas;
  • A expansão no número de membros da classe é uma expansão horizontal;
  • Não aumente o número de membros da classe, mas expanda a versão de um determinado membro da classe, que é expansão vertical, ou seja, substituição;

Exemplo 1: Cadeia de herança

namespace ConsoleHelloWorld {
    
    
    class Program {
    
    
        static void Main(string[] args) {
    
    
            RaceCar raceCar = new RaceCar();
            string owner = raceCar.Owner; // Owner 声明在 Vehicle 当中,在继承链中继承下来了,这个 Owner 是去不掉的
        }
    }

    // 基类
    class Vehicle {
    
    
        public string Owner {
    
     get; set; }
    }

    class Car : Vehicle {
    
     
    
    }

    class RaceCar : Car {
    
     
    
    }
}

Exemplo 2: O que é um objeto de classe base e como acessar os membros da classe base por meio do objeto de classe base

namespace ConsoleHelloWorld {
    
    
    class Program {
    
    
        static void Main(string[] args) {
    
    
            Car car = new Car();
            // 继承链上的类,当我们创建实例的时候,
            // 先从基类的构造器开始,先构造基类对象,再一级一级构造
            // 最终构造创建的子类对象
            car.ShowOwner();
        }
    }

    // 基类
    class Vehicle {
    
    
        // 实例构造器
        public Vehicle(string owner)
        {
    
    
            this.Owner = owner;
        }
        public string Owner {
    
     get; set; }
    }

    class Car : Vehicle {
    
    
        public Car() : base("N/A") // 调用 Vehicle 构造器的时候传入一个值,或者和 Vehicle 构造器的参数列表一致
        {
    
    
            this.Owner = "Car Owner";
        }

        public void ShowOwner() {
    
    
            Console.WriteLine($"this class owner value: {
      
      this.Owner}"); // this. 子类对象上的值
            Console.WriteLine($"base class owner value: {
      
      base.Owner}"); // base. 基类对象上的值,base. 只能向上访问一层
            // 在这个例子中,二者的值是一样的,因为 Car 继承下来了父类的全部成员,
            // 所以 this.Owner 和 base.Owner 指向的使内存中同一个值
        }
    }

    class RaceCar : Car {
    
     
    
    }
}

O nível de acesso dos membros da classe é limitado ao nível de acesso da classe.

Parte 4 Estilo de implementação orientado a objetos

Encapsulamento baseado em classe e polimorfismo de herança;
Encapsulamento baseado em protótipo e polimorfismo de herança;

Parte 5 Reescrita e Polimorfismo

Após herdar da classe pai, a subclasse não aumenta o número de membros, mas substitui os métodos dos membros da classe pai;

Para substituir, você precisa marcar virtual nos membros da classe pai e marcar override nos membros da subclasse.
Se a classe pai não adicionar virtual e a subclasse não adicionar substituição, ela será chamada de subclasse que oculta os membros da classe pai.

O uso de ocultar membros de classe não é comum;
as condições para substituir e ocultar são: membros de função, visíveis (Precisa ser visível para as subclasses, ou seja, só fica visível quando é público ou protegido.), as assinaturas são consistentes

public é visível não apenas para subclasses, mas também para outras classes;
protected é visível apenas para subclasses e não para outras classes;

Polimorfismo: quando usamos variáveis ​​de classe pai para nos referirmos a instâncias de subclasse, ao chamar membros substituídos, sempre podemos chamar os membros mais recentes da classe herdada;Ao chamar um membro do método, o que pode ser chamado é sempre a versão mais recente relacionada ao tipo de instância;

O polimorfismo é baseado no mecanismo de substituição (virtual -> substituição)

Aqui está um exemplo de substituição e polimorfismo:

using System;

namespace HelloRider 
{
    
    
    
    class Program {
    
    
        static void Main(string[] args)
        {
    
    
            var car = new Car();
            car.Run();
            // 可以看到输出为 >> Car is running!
            var vehicle = new Vehicle();
            vehicle.Run();
            // 可以看到输出为 >> I'm running!

            var vehicle2 = new RaseCar();
            vehicle2.Run();
            // 可以看到输出为 >> RaceCar is running!

            // 涉及到多态
            Vehicle v = new Car();
            v.Run(); // 因为引用的是 Car 类型的实例
            // 可以看到输出为 >> Car is running!
            v.Walking(); 
            // 可以看到输出为 >> I'm walking!
            // 因为 Car 类型的 Walking 和 Vehicle 类型的 Walking 并没有重写关系
            // 可以视为这个 Walking 中有两个方法,一个属于 Vehicle 一个属于 Car
        }
    }

    class Vehicle
    {
    
    
        // 重写需要在父类的成员上标注 virtual,并在子类的成员上标注 override
        public virtual void Run()
        {
    
    
            Console.WriteLine("I'm running!");
        }

        public void Walking()
        {
    
    
            Console.WriteLine("I'm walking!");
        }
    }

    class Car : Vehicle
    {
    
    
        // 子类中的方法在父类中也有,并没有新增方法,只是更新了父类方法的版本
        // 子类对父类成员的重写,也叫做成员的纵向扩展
        public override void Run()
        {
    
    
            Console.WriteLine("Car is running!");
        }
        
        // 如果父类不添加 virtual,子类不添加 override,叫做子类对父类成员的隐藏
        public void Walking()
        {
    
    
            Console.WriteLine("Car is walking");
        }
    }

    class RaseCar : Car
    {
    
    
        // 继承链的重写
        public override void Run()
        {
    
    
            Console.WriteLine("RaceCar is running!");
        }
    }
}

O exemplo a seguir é uma substituição de propriedades

using System;

namespace HelloRider 
{
    
    
    
    class Program {
    
    
        static void Main(string[] args)
        {
    
    
            Vehicle vehicle = new Car();
            vehicle.Run();
            Console.WriteLine(vehicle.Speed);
            
        }
    }

    class Vehicle
    {
    
    
        private int _speed;
        
        public virtual int Speed
        {
    
    
            get {
    
     return _speed;}
            set {
    
     this._speed = value; }
        }

        // 重写需要在父类的成员上标注 virtual,并在子类的成员上标注 override
        public virtual void Run()
        {
    
    
            Console.WriteLine("I'm running!");
            this._speed = 100;
        }

        public void Walking()
        {
    
    
            Console.WriteLine("I'm walking!");
        }
    }

    class Car : Vehicle
    {
    
    
        private int _rpm;

        public override int Speed
        {
    
    
            get
            {
    
    
                return this._rpm / 100;
            }
            set
            {
    
    
                this._rpm = value / 100;
            }
        }

        // 子类中的方法在父类中也有,并没有新增方法,只是更新了父类方法的版本
        // 子类对父类成员的重写,也叫做成员的纵向扩展
        public override void Run()
        {
    
    
            Console.WriteLine("Car is running!");
            this._rpm = 5000;
        }
        
        // 如果父类不添加 virtual,子类不添加 override,叫做子类对父类成员的隐藏
        public void Walking()
        {
    
    
            Console.WriteLine("Car is walking");
        }
    }

    class RaseCar : Car
    {
    
    
        // 继承链的重写
        public override void Run()
        {
    
    
            Console.WriteLine("RaceCar is running!");
        }
    }
}

Conhecimento ampliado das aulas da Seção 4

Interfaces e classes abstratas são as partes mais importantes do design orientado a objetos e são os dois pilares do design industrial de software:
princípios SÓLIDOS: princípio de responsabilidade única, princípio aberto-fechado, princípio de substituição de Liskov, princípio de isolamento de interface e princípio de inversão de dependência;

Parte 1 Aula abstrata

O código a seguir explica o que é uma classe abstrata e uma explicação simples do princípio aberto-fechado

namespace HelloRider 
{
    
    
    
    class Program {
    
    
        static void Main(string[] args)
        {
    
    
            
        }
    }
    
    // 抽象类:指的是函数成员没有被完全实现的类
    abstract class Student
    {
    
    
        abstract public void Study(); // 没有被实现的函数成员一定用 abstract 关键字修饰,且不能用 private
        // 被 abstract 修饰的方法,只有返回值,方法名和参数列表,没有方法体,是完全没有被实现的方法,是抽象方法
        // 一旦一个类里有抽象方法或其他抽象成员,这个类就变成了抽象类,抽象类前面必须加上 abstract
        
        // 因为抽象类含有未被实现的成员,因此编译器不允许实例化这个抽象类
        // 两个作用:
        // 1、作为基类,让别人从自己派生出去,在派生类实现没有实现的方法;
        // 2、作为基类,声明变量,用基类类型的变量去引用子类类型的实例;
    }

}

/*
 * 为做基类而生的“抽象类”与“开放/关闭原则”
 * 
 * 开闭原则:我们应该封装一些不变的、稳定的、固定的和确定的成员,
 * 而把那些不确定的、有可能改变的成员声明为抽象成员,并留给子类去实现
 *
 * */

O exemplo a seguir é: "classe abstrata" e "princípio aberto/fechado" criados para classes base:

using System;

namespace HelloRider 
{
    
    
    
    class Program {
    
    
        static void Main(string[] args)
        {
    
    
            Vehicle v = new RaceCar(); 
            // 抽象类的唯一能做的事情就是给别的类当基类,并且引用一些已经完全实现抽象成员的子类实例
            v.Run();
        }
    }

    // 程序既有抽象类,也遵守开闭原则
    abstract class Vehicle
    {
    
    
        public void Stop()
        {
    
    
            Console.WriteLine("Stopped!");
        }

        public void Fill()
        {
    
    
            Console.WriteLine("Pay and fill......");
        }

        public abstract void Run();
    }

    class Car : Vehicle
    {
    
    
        public override void Run() // 实现抽象方法的时候也需要加上 override
        {
    
    
            Console.WriteLine("Car is running......");
        }
    }

    class Truck : Vehicle
    {
    
    
        public override void Run()
        {
    
    
            Console.WriteLine("Truck is running......");
        }
    }

    class RaceCar : Vehicle
    {
    
    
        public override void Run()
        {
    
    
            Console.WriteLine("Race Car is running......");
        }
    }
}

Classe abstrata pura:

using System;

namespace HelloRider 
{
    
    
    
    class Program {
    
    
        static void Main(string[] args)
        {
    
    
            Vehicle v = new RaceCar(); 
            // 抽象类的唯一能做的事情就是给别的类当基类,并且引用一些已经完全实现抽象成员的子类实例
            v.Run();
        }
    }

    // 纯抽象类,在 C# 中实际上就是接口
    abstract class VehicleBase
    {
    
    
        abstract public void Stop();
        abstract public void Fill();
        abstract public void Run();
    }

    // 程序既有抽象类,也遵守开闭原则
    abstract class Vehicle : VehicleBase
    {
    
    
        public override void Stop()
        {
    
    
            Console.WriteLine("Stopped!");
        }

        public override void Fill()
        {
    
    
            Console.WriteLine("Pay and fill......");
        }
    }

    class Car : Vehicle
    {
    
    
        public override void Run() // 实现抽象方法的时候也需要加上 override
        {
    
    
            Console.WriteLine("Car is running......");
        }
    }

    class Truck : Vehicle
    {
    
    
        public override void Run()
        {
    
    
            Console.WriteLine("Truck is running......");
        }
    }

    class RaceCar : Vehicle
    {
    
    
        public override void Run()
        {
    
    
            Console.WriteLine("Race Car is running......");
        }
    }
}

Uma classe abstrata pura é uma interface em C#. Aqui está um exemplo

using System;

namespace HelloRider 
{
    
    
    
    class Program {
    
    
        static void Main(string[] args)
        {
    
    
            Vehicle v = new RaceCar(); 
            // 抽象类的唯一能做的事情就是给别的类当基类,并且引用一些已经完全实现抽象成员的子类实例
            v.Run();
        }
    }

    // 纯抽象类,在 C# 中实际上就是接口
    interface IVehicle
    {
    
    
         void Stop();
         void Fill();
         void Run();
    }

    // 类实现接口
    // 通过 抽象类作为不完全的实现,将其作为基类再创建具体类
    abstract class Vehicle : IVehicle
    {
    
    
        public  void Stop()
        {
    
    
            Console.WriteLine("Stopped!");
        }

        public  void Fill()
        {
    
    
            Console.WriteLine("Pay and fill......");
        }

        abstract public void Run();

    }

    class Car : Vehicle
    {
    
    
        public override void Run() // 实现抽象方法的时候也需要加上 override
        {
    
    
            Console.WriteLine("Car is running......");
        }
    }

    class Truck : Vehicle
    {
    
    
        public override void Run()
        {
    
    
            Console.WriteLine("Truck is running......");
        }
    }

    class RaceCar : Vehicle
    {
    
    
        public override void Run()
        {
    
    
            Console.WriteLine("Race Car is running......");
        }
    }
}
  • O que são interfaces e classes abstratas
    • Interfaces e classes abstratas são produtos da engenharia de software. Se a engenharia de software não for seguida, aumentará a dificuldade de manutenção do código;
    • Classe concreta -> classe abstrata -> interface: cada vez mais abstrata, cada vez menos coisas são implementadas internamente;
    • classe abstrata éNão totalmente implementadoClasses lógicas (podem consistir em campos e membros não públicos, que representam lógica específica);
    • As classes abstratas nascem para reutilização: são utilizadas exclusivamente como classes base e também possuem funções de desacoplamento;
    • Encapsular certas coisas e abrir coisas incertas, adiar a implementação para subclasses apropriadas;
    • A interface éNão implementadoClasses lógicas, classes virtuais puras, possuem apenas membros de função, e todos os membros são públicos e implicitamente públicos;
    • A interface nasceu para desacoplamento: alta coesão, baixo acoplamento, conveniente para testes unitários;
    • A interface é um protocolo conhecido há muito tempo na produção industrial;
    • Eles não podem ser instanciados e só podem ser usados ​​para declarar variáveis ​​e referenciar instâncias de classes concretas;

Parte 2Interface

Interface: evoluída a partir de classes abstratas; os métodos membros na interface devem ser públicos, portanto, ao escrever, não há necessidade de declarar explicitamente público, o padrão é público; a essência da interface: o relacionamento entre o chamador do serviço e
o fornecedor do serviço O contrato deve ser visível para ambas as partes, por isso utiliza-se o público;
A relação entre interface e classe
no mundo abstrato, é a divisão do trabalho e a cooperação entre classes e objetos, e essa cooperação é chamada de dependência. Quando ocorre a dependência, ocorre o acoplamento. Quanto mais direta for a dependência, mais direta será a dependência. Quanto mais estreito for o acoplamento, segue-se um exemplo para explicar a dependência e o acoplamento:

namespace HelloRider 
{
    
    
    
    class Program {
    
    
        static void Main(string[] args)
        {
    
    
            var engine = new Engine();
            var car = new Car(engine);
            car.Run(3);
            Console.WriteLine(car.Speed);
            // 可以看到输出结果为 >> 30
        }
    }

    class Engine
    {
    
    
        public int RPM {
    
     get; private set; }

        public void Work(int gas)
        {
    
    
            this.RPM = 1000 * gas;
        }
    }

    class Car
    {
    
    
        // 此时 Car 和 Engine 类型紧耦合在一起,Car 依赖在 Engine 上
        // 紧耦合的问题在于,基础的类如果出问题,会导致依赖这个类的类出现问题;
        // 紧耦合会导致调试难度升高,这就是紧耦合带来的弊端;
        private Engine _engine;

        public Car(Engine engine)
        {
    
    
            _engine = engine;
        }

        public int Speed {
    
     get; private set; }
        public void Run(int gas)
        {
    
    
            _engine.Work(gas);
            this.Speed = _engine.RPM / 100;
        }
    }
}

Como resolver o problema do acoplamento rígido? Isso é para apresentar uma interface. Aqui está um exemplo:

namespace HelloRider 
{
    
    
    
    class Program {
    
    
        static void Main(string[] args)
        {
    
    
            var user = new PhoneUser(new EricssonPhone()); 
            // 只在这块换了类名,而 User class 还有其他的代码都没变动
            // 引入接口之后,耦合变得非常的松,松耦合
            user.UsePhone();
        }
    }

    class PhoneUser
    {
    
    
        private IPhone _phone;

        public PhoneUser(IPhone phone)
        {
    
    
            _phone = phone;
        }

        public void UsePhone()
        {
    
    
            _phone.Dail();
            _phone.PickUp();
            _phone.Send();
            _phone.Receive();
        }
    }

    interface IPhone
    {
    
    
        void Dail();
        void PickUp();
        void Send();
        void Receive();
    }

    class NokiaPhone : IPhone
    {
    
    
        public void Dail()
        {
    
    
            Console.WriteLine("Nokia calling ...");
        }

        public void PickUp()
        {
    
    
            Console.WriteLine("Hello! This is Tim!");
        }

        public void Send()
        {
    
    
            Console.WriteLine("Nokia message ring ...");
        }

        public void Receive()
        {
    
    
            Console.WriteLine("Hello!");
        }
    }

    class EricssonPhone : IPhone
    {
    
    
        public void Dail()
        {
    
    
            Console.WriteLine("Ericsson calling ...");
        }

        public void PickUp()
        {
    
    
            Console.WriteLine("Hello! This is Jack!");
        }

        public void Send()
        {
    
    
            Console.WriteLine("Ericsson ring ...");
        }

        public void Receive()
        {
    
    
            Console.WriteLine("Good morning!");
        }
    }
}

As interfaces nascem para acoplamento fraco e para compreensão do acoplamento.

Parte 3 Princípio de Teste de Unidade e Inversão de Dependência

O teste unitário é uma aplicação direta do princípio de inversão de dependência no desenvolvimento;
princípio de inversão de dependência: módulos de alto nível não devem depender de módulos de baixo nível, ambos devem confiar em suas abstrações; abstrações (interfaces) não devem depender de detalhes (implementação de funções específicas), detalhes Devem contar com abstração (escrever programas de implementação em formato de interface);
isto éVocê deve programar para a interface, não para a implementação

namespace HelloRider 
{
    
    
    
    class Program {
    
    
        static void Main(string[] args)
        {
    
    
            var fan = new DeskFan(new PowerSupply());
            Console.WriteLine(fan.Work());
        }
    }

    class PowerSupply
    {
    
    
        public int GetPower()
        {
    
    
            return 210;
        }
    }

    class DeskFan
    {
    
    
        private PowerSupply _powerSupply;
        public DeskFan(PowerSupply powerSupply)
        {
    
    
            _powerSupply = powerSupply;
        }

        public string Work()
        {
    
    
            int power = _powerSupply.GetPower();
            if (power <= 0)
            {
    
    
                return "Won't work.";
            }
            else if (power < 100)
            {
    
    
                return "Slow";
            }
            else if (power < 200)
            {
    
    
                return "Work fine";
            }
            else
            {
    
    
                return "Warning!";
            }
        }
    }
}

Você pode ver que o exemplo acima é um exemplo fortemente acoplado.Quando queremos modificar o valor da potência de tensão, precisamos modificar o valor em PowerSupply. Depois que uma classe é projetada, o código da classe não pode ser modificado diretamente. Para resolver esse problema de acoplamento forte, são introduzidas interfaces para desacoplamento e testes unitários são usados ​​para testar o DeskFan. A seguir está o código modificado;

namespace HelloRider 
{
    
    
    
    class Program {
    
    
        static void Main(string[] args)
        {
    
    
            var fan = new DeskFan(new PowerSupply());
            Console.WriteLine(fan.Work());
        }
    }

    public interface IPowerSupply
    {
    
    
        int GetPower();
    }
    public class PowerSupply : IPowerSupply
    {
    
    
        public int GetPower()
        {
    
    
            return 210;
        }
    }

    public class DeskFan
    {
    
    
        private IPowerSupply _powerSupply; // 所有的耦合类型都变成接口类型;
        public DeskFan(IPowerSupply powerSupply)
        {
    
    
            _powerSupply = powerSupply;
        }

        public string Work()
        {
    
    
            int power = _powerSupply.GetPower();
            if (power <= 0)
            {
    
    
                return "Won't work.";
            }
            else if (power < 100)
            {
    
    
                return "Slow";
            }
            else if (power < 200)
            {
    
    
                return "Work fine";
            }
            else
            {
    
    
                return "Explode!";
            }
        }
    }
}

A seguir está um exemplo de teste unitário: é também a aplicação de interfaces em testes unitários;

using NUnit.Framework;
using HelloRider;

namespace InterfaceExample.Tests
{
    
    
    public class DeskFanTests
    {
    
    
        [SetUp]
        public void Setup()
        {
    
    
        }

        [Test]
        public void PowerLowerThanZero_OK() // 测试电压为 0 时的测试结果是否符合预期
        {
    
    
            var fan = new DeskFan(new PowerSupplyLowerThanZero());
            var expected = "Won't work.";
            var actual = fan.Work();
            Assert.AreEqual(expected, actual);
        }
        
        [Test] // 测试例子必须加上 [Test]
        public void PowerHigherThan200_Warning() // 测试电压超过 200 时的测试结果是否符合预期
        {
    
    
            var fan = new DeskFan(new PowerSupplyHigherThan200());
            var expected = "Warning!";
            var actual = fan.Work();
            Assert.AreEqual(expected, actual);
        }
    }

    class PowerSupplyLowerThanZero : IPowerSupply
    {
    
    
        public int GetPower()
        {
    
    
            return 0;
        }
    }
    
    class PowerSupplyHigherThan200 : IPowerSupply
    {
    
    
        public int GetPower()
        {
    
    
            return 220;
        }
    }
}

Insira a descrição da imagem aqui
A imagem acima mostra os resultados da execução do teste de unidade. Você pode ver no canto inferior esquerdo que um caso de teste foi aprovado e um caso de teste falhou. Portanto, você pode interromper a depuração do ponto na parte da amostra de teste onde o problema ocorre;

Parte 4 Princípio de isolamento de interface

A violação do princípio de isolamento da interface tem duas consequências:

Exemplo 1:

namespace HelloRider 
{
    
    
    
    class Program {
    
    
        static void Main(string[] args)
        {
    
    
            var driver = new Driver(new Car());
            driver.Drive();
        }
    }
    
    // 当把包含过多功能的接口类型,传给功能调用者的时候
    // 必然有一部分功能是用不到的,违反了接口隔离原则
    // 实现这个接口的类,同时违反了单一职责原则
    // 针对这个问题的解决方案,把接口拆分为多个小接口,
    // 本质就是把不同的功能分离开,封装成接口

    class Driver
    {
    
    
        private IVehicle _vehicle;

        public Driver(IVehicle vehicle)
        {
    
    
            _vehicle = vehicle;
        }

        public void Drive()
        {
    
    
            _vehicle.Run();
        }
    }

    interface IVehicle
    {
    
    
        void Run();
    }

    class Car : IVehicle
    {
    
    
        public void Run()
        {
    
    
            Console.WriteLine("Car is running");
        }
    }
    
    class Truck : IVehicle
    {
    
    
        public void Run()
        {
    
    
            Console.WriteLine("Truck is running");
        }
    }

    interface ITank
    {
    
    
        void Fire();
        void Run();
    }

    class LightTank : ITank
    {
    
    
        public void Fire()
        {
    
    
            Console.WriteLine("Boom!");
        }

        public void Run()
        {
    
    
            Console.WriteLine("Light Tank is running");
        }
    }
    
    class MediumTank : ITank
    {
    
    
        public void Fire()
        {
    
    
            Console.WriteLine("Boom!!!");
        }

        public void Run()
        {
    
    
            Console.WriteLine("Medium Tank is running");
        }
    }
    
    class HeavyTank : ITank
    {
    
    
        public void Fire()
        {
    
    
            Console.WriteLine("Boom!!!!!!");
        }

        public void Run()
        {
    
    
            Console.WriteLine("Heavy Tank is running");
        }
    }
}

Se você quiser passar a interface ITank neste momento, você precisa modificar o código da classe Driver, o problema neste momento é que uma interface gorda é passada e existe uma função Fire que nunca será usada; agora, esse design viola o princípio de isolamento da interface e a interface gorda Quando a interface é mesclada a partir de duas coisas essencialmente diferentes, a interface gorda deve ser dividida em duas interfaces, ou seja, Fogo e Corrida são separados; Fogo pertence à classe de armas, e Run pertence à classe de veículos motorizados; a seguir está o código modificado

namespace HelloRider 
{
    
    
    
    class Program {
    
    
        static void Main(string[] args)
        {
    
    
            var driver = new Driver(new Car());
            var driver = new Driver(new Truck());
            var driver = new Driver(new HeavyTank());
            // 可以看到,这个 Driver 只要求能跑,修改完代码后,
            // 无论是 Car、Truck还是Tank,都可以跑
            // Driver 只调用 Run,不调用其他无关的,符合了接口隔离原则
            driver.Drive();
        }
    }
    
    // 当把包含过多功能的接口类型,传给功能调用者的时候
    // 必然有一部分功能是用不到的,违反了接口隔离原则
    // 实现这个接口的类,同时违反了单一职责原则
    // 针对这个问题的解决方案,把接口拆分为多个小接口,
    // 本质就是把不同的功能分离开,封装成接口

    class Driver
    {
    
    
        private IVehicle _vehicle;

        public Driver(IVehicle vehicle)
        {
    
    
            _vehicle = vehicle;
        }

        public void Drive()
        {
    
    
            _vehicle.Run();
        }
    }

    interface IVehicle
    {
    
    
        void Run();
    }

    interface IWeapon
    {
    
    
        void Fire();
    }

    class Car : IVehicle
    {
    
    
        public void Run()
        {
    
    
            Console.WriteLine("Car is running");
        }
    }
    
    class Truck : IVehicle
    {
    
    
        public void Run()
        {
    
    
            Console.WriteLine("Truck is running");
        }
    }

    interface ITank : IVehicle, IWeapon // 一个接口对多个接口的继承
    {
    
    
    }

    class LightTank : ITank
    {
    
    
        public void Fire()
        {
    
    
            Console.WriteLine("Boom!");
        }

        public void Run()
        {
    
    
            Console.WriteLine("Light Tank is running");
        }
    }
    
    class MediumTank : ITank
    {
    
    
        public void Fire()
        {
    
    
            Console.WriteLine("Boom!!!");
        }

        public void Run()
        {
    
    
            Console.WriteLine("Medium Tank is running");
        }
    }
    
    class HeavyTank : ITank
    {
    
    
        public void Fire()
        {
    
    
            Console.WriteLine("Boom!!!!!!");
        }

        public void Run()
        {
    
    
            Console.WriteLine("Heavy Tank is running");
        }
    }
}

Quando o princípio do isolamento da interface e o princípio da responsabilidade única são perseguidos demais, serão produzidas muitas interfaces e classes muito fragmentadas com apenas um método.Portanto, é necessário dominar o equilíbrio e manter um tamanho equilibrado de interfaces e classes.

Exemplo 2: A interface gorda passada para o chamador é em si uma fusão de duas interfaces pequenas que foram originalmente bem projetadas. Era para ser passada para uma interface pequena, mas no final foi passada para uma interface grande que fundiu o pequeno interfaces; os provedores de serviços qualificados originais ficam de fora; exemplos e sugestões de modificação abaixo

namespace HelloRider 
{
    
    
    class Program {
    
    
        static void Main(string[] args)
        {
    
    
            int[] nums1 = {
    
    1, 2, 3, 4, 5};
            ArrayList nums2 = new ArrayList {
    
    1, 2, 3, 4, 5};
            Console.WriteLine(Sum(nums1));
            Console.WriteLine(Sum(nums2));
            var nums3 = new ReadOnlyCollection(nums1);
            foreach (var n in nums3)
            {
    
    
                Console.WriteLine(n);
            }
            Console.WriteLine(Sum(nums3)); // 此时 Sum 函数无法处理 nums3
            // 虽然现在只用得到迭代,但现在传入的使 ICollection
            // 把一些合格的 Service Provider 挡在外面
            // 只需要把 Sum 传入的 ICollection 改为 IEnumerable 即可
            // 这时符合接口隔离原则:调用者绝不多要用不着的功能
            // static int Sum(IEnumerable nums)
        }

        static int Sum(ICollection nums)
        {
    
    
            int sum = 0;
            foreach (var num in nums)
            {
    
    
                sum += (int) num;
            }
            return sum;
        }
    }

    // 只能用于迭代,不能添加也不能删除的集合
    class ReadOnlyCollection : IEnumerable
    {
    
    
        private int[] _array;

        public ReadOnlyCollection(int[] array)
        {
    
    
            _array = array;
        }
        
        public IEnumerator GetEnumerator()
        {
    
    
            return new Enumerator(this);
        }
        
        // 成员类
        public class Enumerator : IEnumerator
        {
    
    
            private ReadOnlyCollection _collection;
            private int _head;

            public Enumerator(ReadOnlyCollection collection)
            {
    
    
                _collection = collection;
                _head = -1;
            }
            public bool MoveNext()
            {
    
    
                if (++_head < _collection._array.Length)
                {
    
    
                    return true;
                }
                else
                {
    
    
                    return false;
                }
            }

            public void Reset()
            {
    
    
                _head = -1;
            }

            public object Current
            {
    
    
                get
                {
    
    
                    object o = _collection._array[_head];
                    return o;
                }
            }
        }
    }
}

Exemplo 3:Dedicado a mostrar o que há de único em C#: implementação explícita de interface

namespace HelloRider 
{
    
    
    class Program {
    
    
        static void Main(string[] args)
        {
    
    
            var wk = new WarmKiller();
            wk.Love();
            wk.Kill();
            // 此时,Love 和 Kill 方法都轻易能被调用

            var wk2 = new WarmKillerAAA();
            wk2.Love(); // 此时只能看到 Love 看不到 Killer
            IKiller killer = new WarmKillerAAA();
            killer.Kill(); // 此时才能看到 Killer
            // 如果此时要用 Love 方法
            var wk3 = killer as WarmKiller; // 强制类型转换
            wk3.Love(); // 就能看到 Love 方法了
        }
    }

    interface IGentleman
    {
    
    
        void Love();
    }

    interface IKiller
    {
    
    
        void Kill();
    }

    // 普通的接口隔离与普通的类与实现
    class WarmKiller : IGentleman, IKiller
    {
    
    
        public void Love()
        {
    
    
            Console.WriteLine("I will love you for ever ...");
        }

        public void Kill()
        {
    
    
            Console.WriteLine("Let me kill you ...");
        }
    }
    
    // 普通的接口隔离与普通的类与实现
    class WarmKillerAAA : IGentleman, IKiller
    {
    
    
        public void Love()
        {
    
    
            Console.WriteLine("I will love you for ever ...");
        }

        // 只有把这个类的实例,当做 IKiller 类型的实例的时候,这个方法才能被调用
        // 也就是 只有 Killer 类型的变量来引用 WarmKillerAAA 类,类型的时候,这个方法才能被调用
        void IKiller.Kill() 
        {
    
    
            Console.WriteLine("Let me kill you ...");
        }
    }
}

Parte 5 Reflexão (a ser adicionada)

A reflexão não é uma função da linguagem C#, mas uma função do framework .Net.
O exemplo a seguir é o princípio básico da reflexão


    class Program {
    
    
        static void Main(string[] args)
        {
    
    
            ITank tank = new HeavyTank();
            // ==========================
            // 分割线以下不再使用静态类型
            // 完全是从内存中读取出动态的类型的描述,MetaData 和方法的表述
            // 这块展示的使反射的基本原理
            var t = tank.GetType();
            object o = Activator.CreateInstance(t);
            MethodInfo fireMi = t.GetMethod("Fire");
            MethodInfo runMi = t.GetMethod("Run");
            fireMi.Invoke(o, null);
            runMi.Invoke(o, null);
        }
    }

    class Driver
    {
    
    
        private IVehicle _vehicle;

        public Driver(IVehicle vehicle)
        {
    
    
            _vehicle = vehicle;
        }

        public void Drive()
        {
    
    
            _vehicle.Run();
        }
    }

    interface IVehicle
    {
    
    
        void Run();
    }

    interface IWeapon
    {
    
    
        void Fire();
    }

    class Car : IVehicle
    {
    
    
        public void Run()
        {
    
    
            Console.WriteLine("Car is running");
        }
    }
    
    class Truck : IVehicle
    {
    
    
        public void Run()
        {
    
    
            Console.WriteLine("Truck is running");
        }
    }

    interface ITank : IVehicle, IWeapon // 一个接口对多个接口的继承
    {
    
    
    }

    class LightTank : ITank
    {
    
    
        public void Fire()
        {
    
    
            Console.WriteLine("Boom!");
        }

        public void Run()
        {
    
    
            Console.WriteLine("Light Tank is running");
        }
    }
    
    class MediumTank : ITank
    {
    
    
        public void Fire()
        {
    
    
            Console.WriteLine("Boom!!!");
        }

        public void Run()
        {
    
    
            Console.WriteLine("Medium Tank is running");
        }
    }
    
    class HeavyTank : ITank
    {
    
    
        public void Fire()
        {
    
    
            Console.WriteLine("Boom!!!!!!");
        }

        public void Run()
        {
    
    
            Console.WriteLine("Heavy Tank is running");
        }
    }

No trabalho real, a maioria dos contatos são reflexões encapsuladas.
Uma das funções mais importantes da reflexão encapsulada é a injeção de dependência (DI);

Parte 6 Genérico (a ser adicionado)

Os genéricos estão em toda parte e são equivalentes às interfaces na programação orientada a objetos;
por que os genéricos são necessários? Evite expansão de membros ou expansão de tipo.
Ortogonalidade: tipos genéricos, membros genéricos.
Coisas genéricas não podem ser usadas diretamente durante a programação. Elas precisam ser especializadas antes de poderem ser usadas.
Depois que uma classe genérica é especializada, sempre que parâmetros de tipo são usados, eles são todos fortemente tipados.O
exemplo a seguir é:As classes genéricas não produzem inflação de tipo nem expansão de membros.

namespace HelloRider 
{
    
    
    class Program {
    
    
        static void Main(string[] args)
        {
    
    
            Apple apple = new Apple() {
    
    Color = "Red"};
            Box<Apple> box1 = new Box<Apple>() {
    
    Cargo = apple};
            Console.WriteLine(box1.Cargo.Color);
            // 可以看到输出为 >> Red

            Book book = new Book() {
    
    Name = "New Book"};
            Box<Book> box2 = new Box<Book>() {
    
    Cargo = book};
            Console.WriteLine(box2.Cargo.Name);
            // 可以看到输出为 >> New Book
        }
    }

    class Apple
    {
    
    
        public string Color {
    
     get; set; }
    }

    class Book
    {
    
    
        public string Name {
    
     get; set; }
    }

    class Box<TCargo> // 普通类改成泛型类就是在类名后加上尖括号,在尖括号内写上类型参数
    {
    
    
        public TCargo Cargo {
    
     get; set; }
    }
}

O exemplo a seguir é um exemplo de interface genérica:

namespace HelloRider 
{
    
    
    class Program {
    
    
        static void Main(string[] args)
        {
    
    
            Student<int> student = new Student<int>();
            student.ID = 101;
            student.Name = "Tim";

            Teacher teacher = new Teacher();
            teacher.ID = 1001;
            teacher.Name = "Cheems";
            
        }
    }

    interface IUnique<TId>
    {
    
    
        public TId ID {
    
     get; set; }
    }

    // 如果一个类实现的是泛型接口,那么这个类本身也是泛型的
    // 这个类是实现泛型接口,所以成了泛型类 {第一个方法}
    class Student<TId> : IUnique<TId>
    {
    
    
        public TId ID {
    
     get; set; }
        public string Name {
    
     get; set; }
    }

    // 在实现泛型接口的时候,是实现特化之后的泛型接口,那么这个类就不再是泛型类 {第二个方法}
    class Teacher : IUnique<ulong>
    {
    
    
        public ulong ID {
    
     get; set; }
        public string Name {
    
     get; set; }
    }
}

A razão pela qual os genéricos têm um amplo impacto na programação é que quase todas as estruturas de dados comumente usadas em C# são genéricas;

Acho que você gosta

Origin blog.csdn.net/qq_17790209/article/details/132506946
Recomendado
Clasificación