C#基础知识(一)
前言
温故而知新并非简单地重复过去的学习内容,而是对已有知识进行深入思考和重新审视。当我们回顾旧知识时,可能会发现一些之前被忽视的细节,或者从不同的角度去理解问题。这种重新发现和理解的过程,使我们对知识有了更全面、更深刻的认识。
1. 基础概念与语法:
1.1 数据类型与变量
在C#中,我们使用不同的数据类型来存储不同种类的数据。常见的数据类型包括整数 (int
)、浮点数 (float
)、字符串 (string
) 等。
代码
int age = 25;
float price = 19.99f;
string name = "John";
Console.WriteLine($"Age: {age}, Price: {price}, Name: {name}");
1.2 控制流程(if语句,循环)
控制流程用于控制程序的执行路径。if
语句允许根据条件执行不同的代码块,而循环结构,如 for
循环,可用于重复执行一段代码。
代码:
int number = 10;
if (number > 5)
{
Console.WriteLine("Number is greater than 5");
}
for (int i = 0; i < 5; i++)
{
Console.WriteLine($"Iteration {i}");
}
1.3 函数与方法
在 C# 中,函数和方法是一组执行特定任务的语句。函数通常是指数学上的函数,而方法是面向对象编程中类或对象的成员。C# 中的方法可以有返回值,也可以是无返回值的。
代码
public class Calculator
{
// 无返回值的方法
public void Add(int a, int b)
{
int sum = a + b;
Console.WriteLine($"Sum: {sum}");
}
// 有返回值的方法
public int Multiply(int a, int b)
{
return a * b;
}
}
// 使用方法
Calculator myCalculator = new Calculator();
myCalculator.Add(5, 3); // 输出: Sum: 8
int result = myCalculator.Multiply(2, 4);
Console.WriteLine($"Multiplication Result: {result}"); // 输出: Multiplication Result: 8
1.4 类与对象基础
在 C# 中,类是一种将数据和方法组合在一起的机制,而对象则是类的实例。类可以具有字段(成员变量)和方法,而对象可以使用这些字段和方法。
代码
public class Dog
{
// 字段
public string Name { get; set; }
// 方法
public void Bark()
{
Console.WriteLine("Woof!");
}
}
// 使用类和对象
Dog myDog = new Dog();
myDog.Name = "Buddy";
myDog.Bark(); // 输出: Woof!
2. 面向对象编程(OOP):
2.1 类的继承与多态
继承就好比是家族中的关系,子类继承了父类的特征和行为,就像儿子继承了父亲的姓氏和家传技艺一样。多态则是一种灵活性,它让不同的子类对象可以被当作相同的父类对象来对待,就像不同种类的动物都可以被当作“动物”来对待一样。
代码
using System;
// 定义一个动物类
public class Animal
{
// 虚方法,可以在子类中被重写
public virtual void MakeSound()
{
Console.WriteLine("Generic animal sound");
}
}
// Dog 类继承自 Animal
public class Dog : Animal
{
// 重写了 MakeSound 方法
public override void MakeSound()
{
Console.WriteLine("Woof!");
}
// Dog 类的独有方法
public void WagTail()
{
Console.WriteLine("Tail wagging...");
}
}
// Cat 类也继承自 Animal
public class Cat : Animal
{
// 重写了 MakeSound 方法
public override void MakeSound()
{
Console.WriteLine("Meow!");
}
// Cat 类的独有方法
public void Purr()
{
Console.WriteLine("Purring...");
}
}
class Program
{
static void Main()
{
// 多态性:不同类型的对象被当作相同类型的对象来使用
Animal myDog = new Dog();
Animal myCat = new Cat();
// 调用 MakeSound 方法,实际执行的是各自子类的实现
myDog.MakeSound(); // 输出: Woof!
myCat.MakeSound(); // 输出: Meow!
// 编译时类型是 Animal,运行时类型是 Dog,所以可以调用 WagTail 方法
if (myDog is Dog)
{
((Dog)myDog).WagTail(); // 输出: Tail wagging...
}
// 编译时类型是 Animal,运行时类型是 Cat,所以可以调用 Purr 方法
if (myCat is Cat)
{
((Cat)myCat).Purr(); // 输出: Purring...
}
}
}
这个例子中,Animal
是父类,而 Dog
和 Cat
是它的子类。通过使用多态性,我们可以将它们当作 Animal
对象来使用,并在运行时调用各自子类的特定实现。
2.2 封装与抽象
封装就好比是把事物放进一个盒子里,外部只需要知道如何使用这个盒子,而不需要知道里面的具体细节。抽象则是一种将事物简化的方式,只关注最重要的特征,忽略不重要的细节。
代码
using System;
// 封装的例子
public class BankAccount
{
private decimal balance; // 私有字段,外部无法直接访问
// 存款方法
public void Deposit(decimal amount)
{
balance += amount;
Console.WriteLine($"Deposited: {amount}");
}
// 查询余额方法
public decimal GetBalance()
{
return balance;
}
}
// 抽象的例子
public abstract class Shape
{
// 抽象方法,派生类必须实现
public abstract double Area();
}
public class Circle : Shape
{
private double radius;
public Circle(double r)
{
radius = r;
}
// 实现了抽象类的抽象方法
public override double Area()
{
return Math.PI * radius * radius;
}
}
public class Rectangle : Shape
{
private double width;
private double height;
public Rectangle(double w, double h)
{
width = w;
height = h;
}
// 实现了抽象类的抽象方法
public override double Area()
{
return width * height;
}
}
class Program
{
static void Main()
{
// 封装的使用
BankAccount myAccount = new BankAccount();
myAccount.Deposit(1000);
Console.WriteLine($"Balance: {myAccount.GetBalance()}"); // 输出: Balance: 1000
// 抽象的使用
Shape myCircle = new Circle(5);
Shape myRectangle = new Rectangle(4, 6);
Console.WriteLine($"Circle Area: {myCircle.Area()}"); // 输出: Circle Area: 78.54
Console.WriteLine($"Rectangle Area: {myRectangle.Area()}"); // 输出: Rectangle Area: 24
}
}
在封装的例子中,BankAccount
类封装了存款和查询余额的操作,外部无法直接访问 balance
字段。在抽象的例子中,Shape
类是一个抽象类,定义了一个抽象方法 Area()
,而 Circle
和 Rectangle
类继承并实现了这个方法。
2.3 接口与实现
接口就好比是一份合同,规定了某个类需要实现的一组方法,而实现则是履行了这份合同,提供了这组方法的具体实现。
代码
using System;
// 定义一个日志记录接口
public interface ILogger
{
void LogMessage(string message);
}
// 实现接口的具体类
public class ConsoleLogger : ILogger
{
// 实现接口定义的方法
public void LogMessage(string message)
{
Console.WriteLine($"Log: {message}");
}
}
// 另一个实现接口的具体类
public class FileLogger : ILogger
{
private string filePath;
public FileLogger(string path)
{
filePath = path;
}
// 实现接口定义的方法
public void LogMessage(string message)
{
// 将日志写入文件
System.IO.File.AppendAllText(filePath, $"Log: {message}\n");
}
}
class Program
{
static void Main()
{
// 使用接口
ILogger consoleLogger = new ConsoleLogger();
ILogger fileLogger = new FileLogger("log.txt");
consoleLogger.LogMessage("Console logger message"); // 输出: Log: Console logger message
fileLogger.LogMessage("File logger message"); // 将日志写入文件
// 一个类可以实现多个接口
SomeClass someObject = new SomeClass();
someObject.MethodA();
someObject.MethodB();
}
}
// 一个类实现了多个接口
public class SomeClass : InterfaceA, InterfaceB
{
public void MethodA()
{
Console.WriteLine("MethodA");
}
public void MethodB()
{
Console.WriteLine("MethodB");
}
}
// 两个接口
public interface InterfaceA
{
void MethodA();
}
public interface InterfaceB
{
void MethodB();
}
在这个例子中,ILogger
是一个接口,定义了一个 LogMessage
方法。ConsoleLogger
和 FileLogger
类都实现了这个接口,并提供了具体的方法实现。SomeClass
类实现了两个接口 InterfaceA
和 InterfaceB
。
2.4 抽象类与接口的比较
特征 | 抽象类 | 接口 |
---|---|---|
定义方式 | abstract 关键字定义 | interface 关键字定义 |
构造函数 | 可以有构造函数 | 不能包含构造函数,但可以包含常量、静态成员和属性 |
继承 | 一个类只能继承一个抽象类 | 一个类可以实现多个接口 |
访问修饰符 | 可以包含公共、受保护、内部和私有的成员 | 成员默认为公共,不能包含实现 |
实现 | 可以包含抽象和具体的方法 | 只包含方法签名,没有实现 |
变量 | 可以包含实例变量和常量 | 不能包含实例变量和常量,只能包含常量 |
特殊关键字 | 使用 abstract 和 virtual 关键字 | 使用 interface 关键字 |
3. C# 高级特性:
3.1 异常处理
异常处理就好比是一个紧急出口,当程序遇到问题时,不会直接崩溃,而是会通过异常处理机制跳到一个指定的地方,这样可以更优雅地处理问题。
代码
using System;
class Program
{
static void Main()
{
try
{
int result = Divide(10, 0);
Console.WriteLine($"Result: {result}");
}
catch (DivideByZeroException ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
finally
{
Console.WriteLine("Finally block always executed");
}
}
// 自定义除法函数,可能引发异常
static int Divide(int a, int b)
{
if (b == 0)
{
throw new DivideByZeroException("Cannot divide by zero");
}
return a / b;
}
}
在这个例子中,Divide
函数故意引发了一个除零异常,然后在 try-catch
块中捕获并处理了这个异常。
3.2 泛型编程
泛型就好比是一种通用工具,允许我们编写不关心具体类型的代码。就像一个可变尺寸的工具箱,可以容纳各种大小和形状的工具。
代码
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
// 使用泛型集合 List<T>
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
PrintList(numbers);
// 使用泛型方法
string result = GetResult<string>("Hello, generics!");
Console.WriteLine($"Result: {result}");
}
// 泛型方法,可以用于不同类型的参数
static void PrintList<T>(List<T> list)
{
foreach (var item in list)
{
Console.WriteLine(item);
}
}
// 泛型方法,可以用于不同类型的返回值
static T GetResult<T>(T input)
{
return input;
}
}
在这个例子中,PrintList
方法和 GetResult
方法都是泛型的。List<T>
是泛型集合,它可以存储任意类型的元素。
3.3 LINQ(Language Integrated Query)
当谈到LINQ(Language Integrated Query)时,我们实际上在C#中引入了一种强大的查询语言,它允许我们以一种直观且类似SQL的方式对各种数据源执行查询操作。LINQ 不仅限于数据库查询,它同样适用于内存中的对象集合、XML文档、以及其他数据源
LINQ 可以大大简化数据查询的过程,提高代码的可读性和可维护性。主要分为两个部分:LINQ to Objects(用于查询内存中的对象集合)和 LINQ to SQL(用于查询数据库)。下面是一个针对对象集合的简单例子。
代码
// 创建一个简单的类
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
// 创建一个对象集合
List<Person> people = new List<Person>
{
new Person { Name = "Alice", Age = 25 },
new Person { Name = "Bob", Age = 30 },
new Person { Name = "Charlie", Age = 22 }
};
// 使用 LINQ 查询年龄大于 25 的人员
var result = from person in people
where person.Age > 25
select person;
// 输出结果
foreach (var person in result)
{
Console.WriteLine($"{person.Name} - {person.Age} years old");
}
上述代码展示了 LINQ 的基本结构。from
子句用于指定数据源,where
子句用于筛选条件,select
子句用于选择需要的数据。
此外,LINQ 还支持其他强大的操作,如OrderBy
(排序)、GroupBy
(分组)、Join
(连接)等,让你能够以更直观、简洁的方式处理数据。
这只是 LINQ 的入门,你可以根据实际需求深入研究不同的操作和语法。如果你有特定的 LINQ 操作或问题,欢迎告诉我!
当使用 LINQ 进行增删改查时,我们通常结合集合类(如List<T>
)进行操作。以下是一些示例:
LINQ 查询
假设我们有一个包含学生信息的集合,我们想查询年龄大于等于 18 的学生:
List<Student> students = GetStudents(); // 获取学生集合的方法
var result = from student in students
where student.Age >= 18
select student;
LINQ 新增
要向集合中添加新元素,可以使用 Add
方法:
Student newStudent = new Student { Name = "Eva", Age = 20 };
students.Add(newStudent);
LINQ 删除
删除元素可以使用 Remove
方法,可以按索引或对象进行删除:
// 删除指定对象
students.Remove(newStudent);
// 删除特定索引位置的对象
students.RemoveAt(0);
LINQ 修改
对于修改,你可以直接访问集合中的元素进行更改:
Student firstStudent = students.FirstOrDefault();
if (firstStudent != null)
{
firstStudent.Age = 22;
}
这只是简单的示例,实际应用中可能需要更复杂的逻辑。总的来说,LINQ 与集合的结合提供了强大的工具,使得数据操作更为直观和便捷。
后边如果感兴趣可以单独写一篇关于Linq的文章
4. 文件与输入输出:
4.1 文件读写操作
文件读取
文件读取就好比是打开一本书并逐行阅读,我们可以从文件中读取数据,并逐一处理这些数据。
#> 代码
using System;
using System.IO;
class Program
{
static void Main()
{
// 文件读取
string filePath = "example.txt";
// 使用 StreamReader 逐行读取文件内容
using (StreamReader reader = new StreamReader(filePath))
{
string line;
while ((line = reader.ReadLine()) != null)
{
Console.WriteLine(line);
}
}
}
}
文件写入
文件写入就好比是将我们的想法记录在一张纸上,我们可以将数据写入文件,将程序的输出结果保存起来。
#> 代码
using System;
using System.IO;
class Program
{
static void Main()
{
// 文件写入
string filePath = "output.txt";
// 使用 StreamWriter 写入数据到文件
using (StreamWriter writer = new StreamWriter(filePath))
{
writer.WriteLine("Hello, File I/O!");
writer.WriteLine("This is a new line.");
}
}
}
4.2 数据流处理
数据流是一种以流的形式传输数据的机制。想象一下,当你从一个水龙头中接水,水就会以连续的流动方式传输到你的容器中。同样,数据流是一种让数据以流的方式从一个地方传输到另一个地方的机制。
在文件的输入和输出中,数据流是一种处理文件中数据的方式,它允许我们逐块地读取和写入数据,而不需要一次性加载整个文件到内存中。
代码
using System;
using System.IO;
class Program
{
static void Main()
{
// 数据流的使用
string sourceFilePath = "source.txt";
string destinationFilePath = "destination.txt";
// 使用 FileStream 创建数据流
using (FileStream sourceStream = new FileStream(sourceFilePath, FileMode.Open))
using (FileStream destinationStream = new FileStream(destinationFilePath, FileMode.Create))
{
byte[] buffer = new byte[1024];
int bytesRead;
// 从源文件读取数据到缓冲区,然后写入目标文件
while ((bytesRead = sourceStream.Read(buffer, 0, buffer.Length)) > 0)
{
destinationStream.Write(buffer, 0, bytesRead);
}
}
}
}
在这个例子中,我们使用 FileStream
创建了两个数据流,一个用于读取源文件 (sourceStream
),另一个用于写入目标文件 (destinationStream
)。然后,我们使用一个循环逐块地从源文件读取数据到缓冲区,再将缓冲区中的数据写入目标文件。这样可以有效地处理大型文件,而不需要将整个文件加载到内存中。
Duyi:
5. C# 特性
5.1 属性
属性就像是物体的特征,它们描述了一个对象的某个方面。例如,一辆汽车有颜色属性,这个颜色就是汽车的一个属性。在编程中,属性也是一种描述类或对象特征的方式。
代码
using System;
public class Car
{
// 颜色属性
public string Color { get; set; }
}
class Program
{
static void Main()
{
// 使用属性
Car myCar = new Car();
myCar.Color = "Blue";
Console.WriteLine($"Car Color: {myCar.Color}");
}
}
5.2 事件
事件就好比是一场演出的节目,当某个特定的条件发生时,程序就会执行一些操作。就像在演出中观众喝彩一样,事件可以触发某些代码的执行。
代码
using System;
public class Door
{
// 声明事件
public event EventHandler Opened;
// 方法,模拟门的打开操作
public void Open()
{
Console.WriteLine("Door is opened.");
// 触发事件
Opened?.Invoke(this, EventArgs.Empty);
}
}
class Program
{
static void Main()
{
// 使用事件
Door myDoor = new Door();
// 订阅事件
myDoor.Opened += HandleDoorOpened;
// 模拟打开门的操作
myDoor.Open();
}
// 事件处理程序
static void HandleDoorOpened(object sender, EventArgs e)
{
Console.WriteLine("Someone opened the door!");
}
}
5.3 特性
特性就好比是贴在物体上的标签,它们为代码提供了额外的信息。例如,一本书上可能有标明作者、出版日期等信息的标签,特性在代码中起到类似的作用。
代码
using System;
// 声明一个特性
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public class AuthorAttribute : Attribute
{
public string Name { get; }
public AuthorAttribute(string name)
{
Name = name;
}
}
// 使用特性
[Author("John"), Author("Alice")]
public class MyClass
{
[Author("Bob")]
public void MyMethod()
{
Console.WriteLine("Executing MyMethod");
}
}
class Program
{
static void Main()
{
// 获取并使用特性
Type type = typeof(MyClass);
object[] attributes = type.GetCustomAttributes(typeof(AuthorAttribute), true);
foreach (AuthorAttribute author in attributes)
{
Console.WriteLine($"Author: {author.Name}");
}
// 获取方法上的特性
var methodInfo = type.GetMethod("MyMethod");
var methodAttributes = methodInfo.GetCustomAttributes(typeof(AuthorAttribute), true);
foreach (AuthorAttribute author in methodAttributes)
{
Console.WriteLine($"Author of MyMethod: {author.Name}");
}
}
}
这是关于 C# 中属性、事件和特性的通俗易懂的解释和代码示例。如果有其他需要或者对某个特性有更深入的疑问,随时告诉我。
6. 委托(Delegates)
6.1委托
委托就像是一张通用的招贴,它可以代表某个具体的行动或方法。有点像你请了一位代表去做某事,代表会按照你的指示去完成任务。
在编程中,委托是一种允许你将方法作为参数传递,或者将方法赋值给变量的类型。这使得你可以将方法的执行推迟到稍后的时间,或者传递方法作为参数给其他方法。
代码
using System;
// 定义委托
public delegate void MyDelegate(string message);
public class EventPublisher
{
// 定义事件
public event MyDelegate OnEvent;
// 触发事件的方法
public void RaiseEvent(string message)
{
Console.WriteLine("Event is happening...");
// 触发事件
OnEvent?.Invoke(message);
}
}
public class EventSubscriber
{
// 事件处理程序
public void HandleEvent(string message)
{
Console.WriteLine($"Event handled: {message}");
}
}
class Program
{
static void Main()
{
// 创建委托实例并关联方法
MyDelegate myDelegate = new MyDelegate(DisplayMessage);
// 使用委托
myDelegate("Hello, delegates!");
// 使用委托作为方法参数
ProcessMessage(DisplayMessage, "Hi from method parameter!");
// 使用事件
EventPublisher publisher = new EventPublisher();
EventSubscriber subscriber = new EventSubscriber();
// 订阅事件
publisher.OnEvent += subscriber.HandleEvent;
// 发布事件
publisher.RaiseEvent("Event message");
}
// 委托关联的方法
static void DisplayMessage(string message)
{
Console.WriteLine($"Message displayed: {message}");
}
// 接受委托作为参数的方法
static void ProcessMessage(MyDelegate delegateMethod, string message)
{
delegateMethod(message);
}
}
在这个例子中,MyDelegate
是一个委托类型,它可以代表一个接受一个 string
参数并返回 void
的方法。我们创建了一个委托实例 myDelegate
并将其关联到 DisplayMessage
方法。然后我们演示了如何使用委托,将委托作为参数传递给方法,以及如何使用委托实现事件的发布和订阅模式。
6.2 Action
和 Func
委托
Action
和 Func
是 C# 中两个常用的泛型委托类型。它们提供了一种方便的方式来表示没有返回值的方法(Action
)和有返回值的方法(Func
)。
-
Action
: 代表一个不返回值的方法。就像你让某个人执行某个任务,但不期望他给你任何结果一样。 -
Func
: 代表一个具有返回值的方法。就像你让某个人执行某个任务,并期望他给你一个结果一样。
代码
using System;
class Program
{
static void Main()
{
// Action 示例:表示没有返回值的方法
Action<string> actionDelegate = DisplayMessage;
actionDelegate("Hello, Action!");
// Func 示例:表示有返回值的方法
Func<int, int, int> addFunction = AddNumbers;
int result = addFunction(3, 4);
Console.WriteLine($"Result of adding: {result}");
}
// Action 委托关联的方法
static void DisplayMessage(string message)
{
Console.WriteLine($"Message displayed: {message}");
}
// Func 委托关联的方法
static int AddNumbers(int a, int b)
{
return a + b;
}
}
在这个例子中,Action<string>
表示一个接受一个 string
参数并返回 void
的方法。我们将它关联到 DisplayMessage
方法,并调用它。同样,Func<int, int, int>
表示一个接受两个 int
参数并返回一个 int
的方法。我们将它关联到 AddNumbers
方法,并调用它。
这种泛型委托类型提供了一种灵活的方式来使用不同签名的方法。希望这能帮助你更好地理解 Action
和 Func
委托。
应用场景
Action
委托:
-
异步编程: 在异步编程中,
Action
委托常用于表示异步操作的回调方法。例如,在异步任务完成时,执行某个操作。Action<string> callback = result => Console.WriteLine($"Async operation completed: {result}"); MyAsyncMethod(callback);
-
事件处理: 当你需要定义没有返回值的事件处理方法时,可以使用
Action
。public class Button { public Action OnClick; } // 使用 Button myButton = new Button(); myButton.OnClick += () => Console.WriteLine("Button clicked!");
Func
委托:
-
LINQ 查询: 在 LINQ 查询中,
Func
常用于表示选择器或条件。List<int> numbers = new List<int> { 1, 2, 3, 4, 5 }; var squaredNumbers = numbers.Select(x => x * x);
-
委托链: 当你需要定义一系列具有返回值的方法,并按顺序执行它们时,可以使用
Func
。Func<int, int> addTwo = x => x + 2; Func<int, int> multiplyByThree = x => x * 3; // 使用委托链 int result = addTwo.Then(multiplyByThree).Invoke(5); // 结果为 (5 + 2) * 3 = 21
这些是 Action
和 Func
委托的一些常见应用场景。它们在异步编程、事件处理、LINQ 查询和委托链等方面提供了方便的编码方式。
- 微信关注公众号获取更多学习资料,以及简历模板,面试题