学习C#

下面的案例和代码算不上特别基础,关于基础的C#,网上一大堆,我决定重新看一遍C#本质论,
然后记录下,用来完善我的基础,朋友们可以跳着看

1、开始基础

a、解释下string的不可变

string result;
result="HelloWorld!";

在C#中,string 类型是一个引用类型,这意味着它在堆上分配内存。当你声明一个 string 类型的变量 string result; 时,你创建了一个引用变量 result,但此时它并没有指向任何对象,也就是说它没有指向堆上的任何 string 对象,因此它没有大小、长度或生命周期。

当你执行 result="HelloWorld!"; 这行代码时,发生了以下变化:

堆上分配:在堆上为 "HelloWorld!" 这个字符串分配了内存空间,并且创建了一个 string 对象。
引用赋值:result 这个引用变量被赋值为指向堆上新创建的 string 对象的引用。
大小和长度:此时 result 指向的 string 对象有了大小(占用的内存空间)和长度(字符串中字符的数量,这里是12个字符)。
生命周期:string 对象的生命周期开始,直到没有任何引用指向它,才会被垃圾回收器回收

 internal class Program
 {
     static void Main(string[] args)
     {
         string str = "123";
         str = "456";
         Console.WriteLine(str);
     }
 }

如果你是一个新手,你可以先简单理解下。int i=1;i=2,假设有一个圆圈,代表一块内存,这块内存里面的从1变为了2,表示int是可变的, string str = "123";str = "456";假设有两块内存a和b,123在a中,456在b中,str的变化只是指向了b,但是a内存的123不变,然后这个类Program执行完毕后a内存没有被引用就被垃圾回收
如果你有了一定的基础,我再继续说
在栈上为变量 str 分配内存空间,str 是一个引用,指向一个 string 对象。
在堆上为字符串 "123" 分配内存空间,并创建一个 string 对象。由于 C# 的字符串字面量池机制,如果池中已经存在 "123" 这个字符串,那么 str 将直接引用池中的 "123" 对象

b、分析下C#和java的执行

C# 编译过程:
C# 源代码 (.cs 文件):首先,开发者编写 C# 代码,保存为 .cs 文件。

编译成 CIL (Common Intermediate Language):C# 编译器(如 csc.exe)将 .cs 文件编译成 Microsoft 中间语言(CIL),也称为 MSIL(Microsoft Intermediate Language)。CIL 是一个与平台无关的中间代码,类似于 Java 的字节码。

转换为机器码:然后,CIL 代码通常由 .NET Framework 的 Just-In-Time (JIT) 编译器在运行时转换为特定平台的机器码。这个过程称为 JIT 编译。在 .NET Core 和 .NET 5/6+ 中,也有一个预编译的选项,可以将 CIL 编译为平台特定的机器码。

执行:生成的机器码随后由操作系统执行。

Java 编译过程:
Java 源代码 (.java 文件):开发者编写 Java 代码,保存为 .java 文件。

编译成字节码 (.class 文件):Java 编译器(javac)将 .java 文件编译成 Java 字节码,这是一种与平台无关的中间代码。

运行时转换:Java 字节码不是直接转换为机器码,而是在 Java 虚拟机(JVM)上运行。JVM 是 Java 字节码的运行时环境。

JIT 编译:JVM 中也包含 JIT 编译器,它在运行时将 Java 字节码转换为特定平台的机器码,以提高性能。

执行:转换后的机器码由操作系统执行。

c、说下using的一种用法

using static System.Console;
using static Using关键字.Student;
namespace Using关键字
{
    /// <summary>
    /// using除了释放内存之外,还有using static的使用
    /// </summary>
    internal class Program
    {
        static void Main(string[] args)
        {
            WriteLine("Hello, World!");
            Id = 1;
        }
    }

    public class Student
    {
        public static int Id { get; set; }
    }
}

可以明显的看出,我们打印直接写了WriteLine,还有直接写出了外部类的属性Id

d、理解null和""

string str="";
string str2=null;

不用死记硬背,你就理解电话号码是空和未知电话

e、基元类型静态方法TryParse

 //C#7.0开始不用事先声明out 参数的变量
 var s = int.TryParse("123", out int str3) ? "转换正常":"转换失败";
 Console.WriteLine(s);

这种写法很好,我们可以试着扩展C#没有字符串转化Int,ToInt方法如下

 internal class Program
 {
     static void Main(string[] args)
     {
         string value = "123";
         Console.WriteLine(value.ToInt());
         Console.WriteLine(value.TryToInt(out int number));
     }
    
 }

 public static class StringExtension
 {
     public static int ToInt(this string str)
     {
         int result = 0;
         if (string.IsNullOrWhiteSpace(str))
         {
             return result;
         }
         try
         {
             return int.Parse(str);
         }
         catch (FormatException)
         {
             // 可以选择返回一个特殊值,比如 -1,或者抛出一个新的异常
             return -1; 
         }
     }

     //请注意,[NotNullWhen(true)] 属性通常用于指示当方法返回 true 时,输出参数不为 null。在这个场景中,它并不适用
     public static bool TryToInt(this string str, out int result)
     {
         if (string.IsNullOrWhiteSpace(str))
         {
             result = 0; // 或者你可以选择返回 -1 或其他默认值
             return false;
         }

         // 尝试转换字符串到整数
         return int.TryParse(str, out result);
     }
 }

2、数据类型

1-1、int在栈,string在堆+字面量池

int i=2;
在C#中,int i=2;那么这句代码写了后,i是变量,2是存储的数据,i和数字2都在内存中,int是值类型,所以i和2都在栈中

1-2、操作符

三元运算符?:

public bool IsCheck=>Create();
string str=IsCheck?"运算正确":"运算错误"

空运算符??

 private ObservableCollection<Student> _students;
 public ObservableCollection<Student> Students
 {
     get =>_students ?? _students=new ObservableCollection<Student>();
     set =>_students=value;
 }

空条件符?. 最适合委托书写

PropertyChanged?Invoke(propertyChanged(this,new PropertyChangedEventArgs(nameof(Name))));

按位操作符 (<<>>,|,&,^,~) 适合处理二进制

1-3、多个表达式for循环

for(int x=0,y=5;((x<=5)&&y>=0);x++,y--)
{
  Console.WriteLine($"{x}{((x>y) ? '>' : '<')}{y}\t");
}
0<5
1<4
2<3
3>2
4>1
5>0

1-4 代码的可读性

开发者应注重代码的可读性,而不应该以把过多的精力放在写简短的代码上
现在我们读取本地一个名称为Login.json的文件内容,代码如下

Public class Demo
{
	public void Usage()
	{
		FileQuerier fileQuerier=new FileQuerier()
		{
			FileType=FileType.Json,
			FileName="Login",
		}
		fileQuerier.Load<T>();
	}
}

某些方法过于简单直接使用表达式

public bool IsLogin => (判断登录条件) ? true : false;

1-5 using 别名的使用

尤其是WPF使用HandyControl之后,Window和MessageBox经常与原生的混淆而报编译错误
所以我经常使用HC库后

using HandyOrgWindow=HandyControl.Controls.Window;
using HandyOrgMessageBox = HandyControl.Controls.MessageBox;

这样使用的时候

 HandyOrgMessageBox.Show("登录成功");

1-6 连接地址

使用HttpClient读取网络地址,或者Json读取本地地址我们可以使用Combine方法

  string path1 = "C://";
  string path2 = "index.html";
  var path = Path.Combine(path1, path2);

这样写可以使代码看起来清爽,而不使用+相连接

3、抽象类

1-1 继承

 public class Demo1
 {
     private string Name {  get; set; }
 1、    public Demo1(string name)
     {
         Name= name;
         Console.WriteLine(Name);
     }
 }

 public class SubDemo1 : Demo1
 {
  2、   public SubDemo1(string name) : base(name)
     {
  3、       Console.WriteLine(name);
     }
 }

 public class TestDemo1
 {
     public static void Usage()
     {
         SubDemo1 subDemo1 = new SubDemo1("张三");

     }
 }

上面代码标记的123你觉得执行顺序是怎样的?
答案是213
父类的Name是靠子类传输过去的,基于这种,我想到之前的SqlSugar的IOC注册
它也是将子类的ConnectionObject靠DbScoped传递给父类
具体代码可以看我之前的博客。SqlSugar的连接方式

1-2 protected

protected只能在基类和派生类使用

1-3 继承链

先猜猜下面打印出来是什么

 public class BaseClass
 {
     public void DisplayName()
     {
         Console.WriteLine("第一阶段");
     }
 }

 public class DerivedClass : BaseClass
 {
     public virtual void DisplayName()
     {
         Console.WriteLine("第二阶段");
     }
 }

 public class SubDerivedClass : DerivedClass
 {
     public override void DisplayName()
     {
         Console.WriteLine("第三阶段");
     }
 }

 public class SuperSubDerivedClass : SubDerivedClass
 {
     public new void DisplayName()
     {
         Console.WriteLine("第四阶段");
     }
 }

 public class TestDemo2
 {
     public static void Usage()
     {
         SuperSubDerivedClass superSubDerivedClass = new SuperSubDerivedClass();         
         SubDerivedClass subDerivedClass=superSubDerivedClass;                           
         DerivedClass derivedClass= superSubDerivedClass;                               
         BaseClass baseClass = superSubDerivedClass;                              
         superSubDerivedClass.DisplayName();
         subDerivedClass.DisplayName();
         derivedClass.DisplayName();
         baseClass.DisplayName();
     }
 }

答案是,四 三 三 一
1、,BaseClass baseClass = superSubDerivedClass;相当于多态,所以baseClass 调用方法打印的结果是第一阶段,
2、superSubDerivedClass.DisplayName(); 输出 "第四阶段",这是它本身new的自己,并且它没有子类,打印自己的方法没问题,
3、subDerivedClass.DisplayName(); 输出 "第三阶段",它使用override,重写了父类的方法,打印本身没问题,
4、关键是derivedClass.DisplayName(); 输出 "第三阶段"也是相当于多态,按理来说打印的应该是本身,
但是它使用了virtual,它的子类使用了override,所以它打印的是自己子类的输出,也就是第三阶段

1-4 多态与switch的组合使用

 public static void Save(Storage storage)
 {
     switch(storage) 
     {
         case null:
             throw new ArgumentNullException(nameof(storage));

         case Usb usb when usb.IsPluggedIn:
             usb.Unload();
             Console.WriteLine("USB Drive Unloaded");
             break;
         case Dvd dvd when dvd.IsInserted:
              dvd.Eject();
             Console.WriteLine("DVD Ejected");
             break;
     }
 }
 
  public class Usb : Storage
 {
     public bool IsPluggedIn { get; private set; }

     public void Unload()
     {
         // 实现USB卸载逻辑
     }
 }

 public class Dvd : Storage
 {
     public bool IsInserted { get; private set; }

     public void Eject()
     {
         // 实现DVD弹出逻辑
     }
 }

 public abstract class Storage
 {
     // Storage类可能包含一些所有存储设备共有的属性和方法
 }

4、接口

1-1 显式接口

  public interface IReadableService
  {
      string GetSetting(string name,string defaultValue);
  }

  public interface ISettingProvider:IReadableService
  {
      void Setting(string name,string defaultValue);
  }

  public class FileSettingProvider : ISettingProvider, IReadableService
  {
      public string GetSetting(string name, string defaultValue)
      {
          throw new NotImplementedException();
      }

      public void Setting(string name, string defaultValue)
      {
          throw new NotImplementedException();
      }

      string IReadableService.GetSetting(string name, string defaultValue)
      {
          throw new NotImplementedException();
      }
  }

隐藏实现细节:当您不希望类的外部访问者直接调用接口中定义的方法,而是希望提供更具体的实现时,可以使用显式接口实现。

解决命名冲突:当类实现了多个接口,并且这些接口中有相同签名的方法时,显式接口实现可以用来区分这些方法的不同实现。

多态性:显式接口实现允许类以不同的方式实现同一个接口中的方法,这可以提供更细粒度的控制。

1-2 版本控制

如组件或应用程序正在供其他开发者使用,创建新版本时,不要修改接口。接口在实现接口的类和使用接口的类之间订立了契约,修改接口相当于修改契约,会使接口写的代码失效。

1-3 接口和特性

有时无任何成员的接口(不管是不是继承的)来描述关于类型的信息。例如,有人会创建IObsolete来标记接口指出某类型已被另一个类型取代,一般认为这是对接口机制的"滥用",接口应表示类型能执行的功能,而非陈述关于类型的事实。这时不要使用接口,使用特性,这是C#本质论的原话。
Furion框架自从收费以后被很多人诟病,它不是一个框架而是一个工具程序集的集合
它里面空的接口太多,对接口造成了滥用

1-4 抽象类和接口的选择

抽象类 接口
不能直接实例化,只能实例化一个派生类 不能直接实例化,只能实例化一个实现类型
派生类要么自己也是抽象的,要么必须实现所有抽象成员 实现类型必须实例化所有接口成员
可添加额外的非抽象成员,由所有派生类继承,不会破坏跨版本兼容性 为接口添加额外的成员会破坏版本兼容性
可声明方法,属性和字段(以及其他成员类型,包括给构造函数和终结器) 可声明方法和属性但不能声明字段、构造函数或终结器
成员可以是实例、虚、抽象、或静态、非抽象成员可以提供默认实现供派生类使用 所有成员都基于实例(而非静态),而且自动视为抽象,所以不能包括任何实现
派生类只能从一个基类派生(继承) 实现类型可实现任意多的接口

需要注意的是,我上面的表格是C#7.0本质论写的,现在的C#接口可以实现静态的属性,可以写自己的方法,随着版本的更新,它不是准确的,你们要自己辨别

5、泛型

1-1、泛型类

 public class Sample1<T>
 {
     public int IComparer(T t)
     {
         return -1;
     }
 }

1-2、泛型方法

  public class Sample2
  {
      public int IComparer<T>(T t)
      {
          return -1;
      }
  }

1-3、泛型接口

 public interface IPair<T>
 {
     T First { get; set;  }
     T Second { get; set; }
 }

 public class Pair<T> : IPair<T>
 {
     public Pair(T first,T second)
     {
         First = first;
         Second = second;
     }

   public  T First { get; set; }
   public  T Second { get; set; }
 }

但是这会出现一个问题,泛型Pair构造器如果只有一个参数,Second没办法写默认值,因为不知道T的类型。
这个时候default关键字就出来了

 public Pair(T first)
 {
     First = first;
     Second = default(T);
 }

也可以使用多个泛型参数

 public interface IStudent<TFirst, TSecond>
 {
     TFirst First { get; set; }
     TSecond Second { get; set; }
 }

1-4、泛型约束

 public class BinaryTree<T>
 {
     public T Item { get; set; }

     public Pair<BinaryTree<T>> _subItems;
     public Pair<BinaryTree<T>> SubItems 
     {
         get => _subItems;
         set
         {
             IComparable<T> first;
             first = (IComparable<T>)value.First.Item;
             if (first.CompareTo(value.Second.Item)<0)
             {

             }
             _subItems = value;
         }
     }
 }

1-5、构造函数约束

public Student<TKey,TValue>() where TKey:IComparable<TKey> where TValue:EntityBase<TKey>,new()
{

}

并非所有的对下个都有公共的默认构造函数,new关键字添加后,要求实参必须要有默认的构造函数,它不能对有参构造函数指定约束。
泛型促进了类型安全,确保在参数化的类中,只有成员明确希望的数据类型才能可使用。
泛型缓解了代码膨胀,既保持了具体版本的优势,又没有具体版本的开销。
性能得以提高,一个原因是不再需要从object的强制转换,从而避免了类型检查,另一个原因是不需要为值类型装箱。
内存消耗减少,由于避免了装箱,因此减少了堆上的内存消耗。
最核心的是,泛型允许写代码来实现模式,并在以后出现这种模式的时候重用该实现。而泛型为这些反复出现的模式提供了单一的实现。

/// <summary>
/// 来考虑一个恒温控制的例子,一个加热器、一个冷却器、一个恒温器
/// </summary>
 public class TestDemo
 {
     public static void Usage()
     {
         Thermostat thermostat = new Thermostat();
         Heater heater=new Heater(60);
         Cooler cooler = new Cooler(80);
         thermostat.OnTemperatureChange += heater.OnTemperatureChanged;
         thermostat.OnTemperatureChange += cooler.OnTemperatureChanged;

         Console.WriteLine("------------开始调节温度---------------");
         while (true)
         {
             Console.Write("请输入当前温度: ");
             string input = Console.ReadLine();

             if (input.ToLower() == "exit")
             {
                 Console.WriteLine("程序退出。");
                 break; // 退出循环
             }

             if (int.TryParse(input, out int temperature))
             {
                 thermostat.CurrentTemperature = temperature;
             }
             else
             {
                 Console.WriteLine("输入无效,请输入一个数字或 'exit' 退出。");
             }
         }
     }
 }
 /// <summary>
 /// 订阅者
 /// </summary>
 public class Cooler
 {
     public float Temperature {  get; set; }
     public Cooler(float temperature)
     {
         Temperature = temperature;
     }

     public void OnTemperatureChanged(float newTemperature)
     {
         if (newTemperature>Temperature)
         {
             Console.WriteLine("冷却器打开");
         }
         else
         {
             Console.WriteLine("冷却器关闭");
         }
     }
 }

 /// <summary>
 /// 订阅者
 /// </summary>
 public class Heater
 {
     public float Temperature { get; set; }
     public Heater(float temperature)
     {
         Temperature = temperature;
     }

     public void OnTemperatureChanged(float newTemperature)
     {
         if (newTemperature < Temperature)
         {
             Console.WriteLine("加热器打开");
         }
         else
         {
             Console.WriteLine("加热器关闭");
         }
     }
 }

 /// <summary>
 /// 发布者
 /// </summary>
 public class Thermostat
 {
     public Action<float> OnTemperatureChange {  get; set; }

     private float _currentTemperature;
     public float CurrentTemperature 
     {
         get => _currentTemperature;
         set
         {
             if (value !=_currentTemperature)
             {
                 _currentTemperature = value;
                 OnTemperatureChange(value);
             }
         }
     }
 }
posted @ 2024-05-29 23:02  孤沉  阅读(14)  评论(0编辑  收藏  举报