作者:傻响
专栏:《.Net语法类》
格言:你只管努力,剩下的交给时间!
目录
C# 类(Class)
类的概念
C#类的定义
1、语法
2、类修饰符
3、引出封装
C#访问修饰符
1、语法
2、方法语法各部分说明
3、方法参数
4、方法修饰符
C#构造函数
语法
C#析构函数
语法
C#继承
语法
C#静态成员
语法
C#静态类
语法
多态
1、什么是多态
2、方法重载
3、虚方法
4、new的隐藏
5、抽象类
6、抽象方法
接口
接口Interface定义
接口Interface实现
接口与抽象类对比
C#是面向对象的语言,使用类来进行抽象,类是对象在面向对象编程语言中的反映。类从客观事务中抽象和总结出来的“蓝图”,类描述了一系列在概念上有相同含义的对象,并为这些对对象统一定义了各种成员。
类是一种数据结构,可以包含数据成员(常量和域)、函数成员(方法、属性、事件、索引器、构造函数和析构函数)和嵌套类型等。
// 类使用关键字class来声明
public class Car--类名 public--访问修饰符 还有:protected private internal
{
public Car(){ } --构造函数
public event EventHandler StartEvent; --事件
public int number; --字段
public string Color{get;set;} --属性
public void Start() --方法
{
}
abstract 抽象的 --抽象类
static 静态的 --静态类
sealed 密封的 --密封类
// 注意
注意:
构造函数默认为public,
析构函数不能显式使用访问修饰符且默认为private,
类成员默认访问修饰符为private;
类的默认访问修饰符事Internal
封装——隐藏对象的属性实现细节,仅对外公开接口,控制在程序中属性的读和修改的访问级别;将抽象得到的数据和行为(或功能)相结合,形成一个有机的整体,也就是将数据与操作数据的源代码进行有机的结合,成“类”,其中数据和函数都是类的成员。
简言之:将数据或方法等集合在一个个的单元中——这就是类。
C#封装根据具体的需求,设置各成员的访问权限来控制成员的可见性——使用访问修饰符实现
// 默认的访问修饰符是internal
internal class ItemInfo
{
// 类中定义:类别、编号、名称的属性
// 无参构造函数
public ItemInfo()
{
}
// 带参构造函数
public ItemInfo(string itemType,int itemId, string itemName)
{
this.ItemType = itemType;
this.ItemId = itemId;
this.ItemName = itemName;
}
// 完整的属性封装
private string itemType; // 称为 - 字段
public string ItemType
{
get { return ItemType; } // 外面获取
set { ItemType = value; } // 外面设置
}
// 自动的属性封装
public int ItemId { get; set; }
public string ItemName { get; set; }
// 析构函数 析构函数是在程序类在释放销毁的时候才会使用。
~ItemInfo()
{
}
}
// 基于winfrom进行程序调用
private void button_Add_Click(object sender, EventArgs e)
{
// 获取类别
string type = textBox_Type.Text.Trim();
// 获取编号
int id = int.Parse(textBox_Id.Text.Trim());
// 获取名字
string name = textBox_Name.Text.Trim();
// 调用类进行属性封装。
ItemInfo itemInfo = new ItemInfo(); // 待用无参构造函数。
itemInfo.ItemType = type;
itemInfo.ItemId = id;
itemInfo.ItemName = name;
}
C#语言中,类和类成员都具有访问级别,用来控制是否在程序集的其他代码中或其他程序集中使用它们的权限。给它们指定不同的访问修饰符,就具有不同的访问级别。
C#语言中中涉及访问修饰的关键字一共有四个,组成了5种()不同的访问级别,分别是:public、private、internal、protected和组合修饰符protected internal。
当我们在名字空间中定义的时候,一般声明的是类型,我们可用的只有public和internal两种。
语法:
访问修饰符 [修饰符] 返回值类型 方法名(参数列表)
{
语句块;
}
// 参数的方法
public void ShowIntemInfo()
{
}
// 带返回值的方法
public string GetItemInfo(ItemInfo itemInfo)
{
return $"类别:{itemInfo.ItemType},编号:{itemInfo.ItemId},名目:{itemInfo.ItemName}\r\n";
}
// 列表打印
label_List.Text += itemInfo.GetItemInfo(itemInfo);
访问修饰符:public、 private、 protected,对于另一个类的可见性,默认是private。
方法修饰符: virtual (虚拟的)、abstract(抽象的)、override(重写的)、new(隐藏)、static(静态的)、sealed(密封的)。
返回值类型:用于指定返回结果的数据类型,可以是任意的数据类型,如果指定了返回值类型,必须使用return关键字返回一个与之类型匹配的值,没有返回值,使用void
方法名:对方法所实现功能的描述。方法名的命名是以Pascal命名法为规范的。(首字母大写)
参数列表:在方法中允许有0到多个参数,如果没有指定参数也要保留参数列表的小括号
方法参 数类型:
值参数:不包含任何修饰符,将实参的值复制到形参的方式把数据传递,形参的改变不会影响到内存中实参的的值,实参是安全的
引用参数:以ref修饰符声明,调用时也要使用ref,实参必须是变量,传递是实际上是实参的引用,传递前必须进行初始化。
输出参数:以out修饰符声明,调用时也要使用out, out参数声明方式不要求变量传递给方法前进行初始化,因为它的含义只是用作输出目的。但是,在方法返回前,必须对out参数进行赋值。
// 带返回值和两个参数的方法。
public int Add(int a, int b)
{
return a + b;
}
// 带引用参数
public int Add(ref int a , ref int b)
{
a = 10; // 外部传参跟着改变.
b = 20; // 外部传参跟着改变.
return a + b;
}
// 带输出参数
public int Add(int a, int b, out int c)
{
a = 10;
b = 20;
c = a + b; // 输出到外部形参
return a + b + c;
}
static 该方法是类成员,不是类实例成员。调用方法:类名.方法名(实参列表),而不是创建类实例。
virtual 指示该方法可以在子类中覆盖(重写),它不能与static或private访问该修饰符一同使用。
override 指示该方法覆盖了基类中的同名方法,能定义子类特有的行为。基类中被覆盖的方法必须用virtual
new 继承类中的一个方法“隐藏”基类中同名的方法。它会取代原方法,而不是覆盖
sealed 禁止派生类重写此方法
abstract 该方法为抽象方法,不包含具体实现细节,必须由子类实现。只能用做abstract类的成员。
构造函数:是在创建给定类型的对象时执行的类方法。
构造函数与类同名,通常用于初始化新对象的数据成员或初始化处理。
构造函数分为无参构造函数、带参数的构造函数。
//--无参构造函数
public ItemInfo()
{
}
//--有参构造函数 封装名目信息
public ItemInfo(int id,int typeId,string typeName,string itemName)
{
ItemNo = id;
TypeId = typeId;
ItemType = typeName;
ItemName = itemName;
}
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// 类中定义:类别、编号、名称的属性
// 无参构造函数
public ItemInfo()
{
}
// 带参构造函数
public ItemInfo(string itemType,int itemId, string itemName)
{
this.ItemType = itemType;
this.ItemId = itemId;
this.ItemName = itemName;
}
析构函数:是类的一个特殊的成员函数。析构函数的名称是在类的名称前面加上“~”作为前缀,没有返回值,不带任何参数。
析构函数用于在某个类被认为不在有效符合析构条件时(调用完毕、关闭程序等),一般析构函数里面写的都是一些资源回收之类的东西. C#中的析构函数并不会每次都会调用,所以最好不要用析构函数来回收资源
注:一个类只能有一个析构函数且无法手动调用,它是被自动调用的
// 析构函数:一个类只能有一个析构函数且无法手动调用,它是被自动调用的。
~ItemInfo()
{
}
继承是面向对象程序设计中最重要的概念之一,继承——根据一个现有类来定义新类
好处:使得创建和维护应用程序变得更容易;也有利于重用代码和节省开发时间。
当创建一个新类时,如果这个类的某些成员和一个现有类相同。程序员不需要完全重新编写新的数据成员和成员函数,只需要创建一个新的类,继承这个现有类即可。
这个已有的类被称为的基类,这个新的类被称为派生类。
// 父类
public class RecordParent
{
public int Id { get; set; } // id
public string Type { get; set; } // 类型
public string Name { get; set; } // 姓名
public string Describe { get; set; } // 描述
public decimal Amount { get; set; } // 金额
}
// 派生类 - 收入
public class IncomeRecord : RecordParent
{
public void Show()
{
MessageBox.Show($"编号:{Id+100} 类型:{Type} 名称:{Name} 描述:{Describe} 金额:{Amount}");
}
}
// 派生类 - 支出
public class ExpendRecord : RecordParent
{
public int count { get; set; } // 数量
public decimal price { get; set; } // 单价
public void Show()
{
MessageBox.Show($"编号:{Id + 100} 类型:{Type} 名称:{Name} 描述:{Describe} 数量:{count} 单价:{price} 金额:{Amount}");
}
}
// 调用
private void FormHome_Load(object sender, EventArgs e)
{
IncomeRecord inCome = new IncomeRecord();
inCome.Id = 1;
inCome.Type = "收入";
inCome.Name = "工资";
inCome.Describe = "9月分工资";
inCome.Amount = 5500.0M;
inCome.Show();
ExpendRecord expend = new ExpendRecord();
expend.Id = 1;
expend.Type = "支出";
expend.Name = "购物";
expend.Describe = "买一台电视机";
expend.Amount = 2200.0M;
expend.count = 2;
expend.price = 22M;
expend.Show();
}
static ——成员是类成员,不是实例成员。静态变量、静态属性、静态方法等
静态变量——定义公共变量,它们的值可以通过直接调用类而不需要创建类的实例来获取,一般用于存共享的数据,在内存中只有一份,直到应用程序退出才释放。
静态变量可在成员函数或类的定义外部进行初始化,你也可以在类的定义内部初始化静态变量。
静态方法:你也可以把一个成员函数声明为 static。这样的函数内部只能访问静态变量,但只能定义非静态局部变量。
可以在静态类或实例类中声明静态成员。 封装通用处理方法
静态函数在对象被创建之前就已经存在,不能在方法内部声明静态成员。
// 在普通类中可以定义静态成员:静态属性、静态方法、静态变量、静态构造函数。
public class CommonVariable
{
// 静态构造函数
public CommonVariable()
{
}
// 静态变量
public static int TotalCount = 0;
public static decimal TotalAmount = 2.0M;
public static decimal TotalIncomeAmount = 1.0M;
public static decimal TotalExpendAmount = 1.0M;
// private static int recon; // 一边不会定义私有的变量。
// 静态方法
public static void ShowInfo()
{
int reEnxpend = 0; // 静态方法可以定义普通的变量。
// 不允许定义静态变量。
MessageBox.Show($"总数量:{TotalCount} 总金额:{TotalAmount} 收入金额:{TotalIncomeAmount} 支出金额:{TotalExpendAmount}");
}
}
// 调用
CommonVariable.ShowInfo();
// 利用对静态C成员和方法的封装,可以写一些常用的工具。
public class CommonHelper
{
///
/// 将字符串转换为整型。
///
///
public static int StringToInt(string val)
{
int temp = 0;
int.TryParse(val, out temp);
return temp;
}
///
/// 将字符串转换为单精度浮点型。
///
///
public static float StringToFloat(string val)
{
float temp = 0;
float.TryParse(val, out temp);
return temp;
}
}
关键字 static ——可以把类定义为静态的。
当我们声明一个类为静态时,该类不能实例化,其成员也必须是静态的。
静态类特征:
只包含静态成员;
无法实例化;
无法派生子类 ;
不包含实例构造函数;
可以包含静态构造函数
静态类:封装通用方法、定义扩展方法、静态成员-----重用
public static class CommonHelper
{
--静态方法。。。。
}
///
/// 静态类
///
public static class CommonHelper
{
///
/// 将字符串转为双精度浮点数 - 扩展方法 - 关键字this
///
///
///
public static double StringToDouble(this string val)
{
double temp = 0.0;
double.TryParse(val, out temp);
return temp;
}
///
/// 将字符串转为整型 - 扩展方法 - 关键字this
///
///
///
public static int StringToInt(this string val)
{
int temp = 0;
int.TryParse(val, out temp);
return temp;
}
///
/// 将整型数组转为字符串 - 扩展方法 - 关键字this
///
///
///
public static string IntArrToString(this int[] val)
{
return string.Join(",", val);
}
}
// 扩展方法调用
double drVar1 = str1.StringToDouble();
int iVar1 = str2.StringToInt();
int[] iVar3 = { 1, 2, 3, 4, 5, 6 };
string str4 = iVar3.IntArrToString();
多态是同一个行为具有多个不同表现形式或形态的能力。
通俗地说:同一操作作用于不同的对象,产生不同的结果。
多态意味着有多重形式。在面向对象编程范式中,多态性往往表现为"一个接口,多个功能"。
在 C# 中,每个类型都是多态的,因为包括用户定义类型在内的所有类型都继承自 Object。
多态性分为两种:
静态多态——编译时的多态----方法重载。
动态多态——运行时的多态----重写(虚方法、抽象方法)。
静态多态:根据传递的参数、返回类型等决定执行的操作
方法重载: ——同一个范围内对相同的函数名有多个定义。函数的定义必须彼此不同,可以是参数列表中的参数类型不同,也可以是参数个数不同。不能重载只有返回类型不同的函数声明。
动态多态:直到系统运行时,才决定实现何种操作。
通过 抽象方法 和 虚方法 实现的。
重载:类中定义方法的不同版本,方法名相同,参数列表不同,返回值类型可以不相同
特点(两必须一可以)
方法名必须相同
参数列表必须不相同
返回值类型可以不相同
// 方法重载1
private int Add(int x, int y)
{
return x + y;
}
// 方法重载2
private decimal Add(int x, int y, int z)
{
return (decimal)x + y + z;
}
// 方法重载3
private decimal Add(decimal x, decimal y, decimal z)
{
return x + y + z;
}
虚方法:在基类中,一个方法前面用virtual修饰,并可以在子类中用override重写的方法。
特点:
必须用virtual修饰;
用override重写,可以在子类重写,也可以不重写
方法体必须有,哪怕空的也行;
不能使用sealed修饰,否则不能重写
要求(三相同)
相同的方法名称
相同的参数列表
相同的返回值类型
// 在普通类中可以定义静态成员:静态属性、静态方法、静态变量、静态构造函数。
public static class CommonVariable
{
// 总记录次数
public static int TotalCount = 0;
// 总收入金额
public static decimal TotalIncomeAmount = 0.0M;
// 总支出金额
public static decimal TotalExpendAmount = 0.0M;
// 总收入支出金额
public static decimal TotalInExAmount = 0.0M;
}
///
/// 统计基类
///
public class StatisticsBase
{
// 收入金额
public decimal InTotal { get; set; }
// 支出金额
public decimal ExTotal { get; set; }
// 收支金额
public decimal InExTotal { get; set; }
// 计算收支虚方法
public virtual void CallInExAmount()
{
;
}
}
// 日统计类继承统计基类。
public class DayStatistic : StatisticsBase
{
// 日期
public string Date { get; set; }
// 虚方法重写
public override void CallInExAmount()
{
CommonVariable.TotalIncomeAmount += InTotal;
CommonVariable.TotalExpendAmount += ExTotal;
CommonVariable.TotalInExAmount += InTotal - ExTotal;
}
}
// 周统计类继承统计基类。
public class WeekStatistics : StatisticsBase
{
public string Week { get; set; }
public decimal[] InTotalArr { get; set; }
public decimal[] ExTotalArr { get; set; }
public override void CallInExAmount()
{
InTotal = InTotalArr.Sum();
ExTotal = ExTotalArr.Sum();
CommonVariable.TotalIncomeAmount += InTotal;
CommonVariable.TotalExpendAmount += ExTotal;
CommonVariable.TotalInExAmount = InTotal - ExTotal;
}
public new void CallAmount()
{
InExTotal = InTotal - ExTotal + 100;
}
}
// 在普通类中可以定义静态成员:静态属性、静态方法、静态变量、静态构造函数。
public static class CommonVariable
{
// 总记录次数
public static int TotalCount = 0;
// 总收入金额
public static decimal TotalIncomeAmount = 0.0M;
// 总支出金额
public static decimal TotalExpendAmount = 0.0M;
// 总收入支出金额
public static decimal TotalInExAmount = 0.0M;
}
///
/// 统计基类
///
public class StatisticsBase
{
// 收入金额
public decimal InTotal { get; set; }
// 支出金额
public decimal ExTotal { get; set; }
// 收支金额
public decimal InExTotal { get; set; }
// 计算收支虚方法
public virtual void CallInExAmount()
{
;
}
public void CallAmount()
{
InExTotal = InTotal - ExTotal - 10;
}
}
// 日统计类继承统计基类。
public class DayStatistic : StatisticsBase
{
// 日期
public string Date { get; set; }
// 虚方法重写
public override void CallInExAmount()
{
CommonVariable.TotalIncomeAmount += InTotal;
CommonVariable.TotalExpendAmount += ExTotal;
CommonVariable.TotalInExAmount += InTotal - ExTotal;
}
public new void CallAmount()
{
InExTotal = InTotal - ExTotal + 10;
}
}
// new 和 override的调用情况
DayStatistic Day_01 = new DayStatistic()
{
Date = "2022-10-13",
InTotal = 500,
ExTotal = 400
};
Day_01.CallInExAmount(); // override 调用子类的方法
Day_01.CallAmount(); // new 调用子类的方法
StatisticsBase Day_02 = new StatisticsBase()
{
InTotal = 500,
ExTotal = 400
};
Day_02.CallInExAmount(); // override 调用父类的方法
Day_02.CallAmount(); // new 调用父类的方法
StatisticsBase Day_03 = new DayStatistic()
{
Date = "2022-10-13",
InTotal = 500,
ExTotal = 400
};
Day_03.CallInExAmount(); // override 调用子类的方法
Day_03.CallAmount(); // new 调用父亲类的方法
C#允许使用关键字 abstract 创建抽象类。当一个派生类继承自该抽象类时,必须实现该抽象类中的抽象方法。
抽象类的一些规则:
不能直接创建一个抽象类的实例。
不能在一个抽象类外部声明一个抽象方法。
抽象类不能被声明为 sealed。
抽象类可以包含抽象方法,抽象方法可被派生类实现
抽象类可以定义普通方法、虚方法,并可以有实现。
// 抽象类 - 定义
public abstract class AbstractClass
{
// 收入金额
public decimal InTotal { get; set; }
// 支出金额
public decimal ExTotal { get; set; }
// 收支金额
public decimal InExTotal { get; set; }
// 支持虚方法
public virtual void CallInExAmount()
{
;
}
// 支持普通方法
public void CallAmount()
{
InExTotal = InTotal - ExTotal - 10;
}
// 抽象方法,不能对抽象方法进行实现。
public abstract void Show();
public abstract void ShowInfo();
}
C# 允许使用关键字 abstract 修饰方法——抽象方法。
特点:
只能在抽象类中定义,在抽象类的派生类中实现。
抽象方法实现用override,是隐式的虚方法
实现抽象方法的方法不能是抽象成员。
抽象方法与虚方法区别:
关键字不同:抽象方法 abstract 虚方法:virtual;
定义位置要求:抽象方法必须在抽象类中,虚方法不需要(抽象类或普通类均可)
默认实现:抽象方法不能有实现,虚方法必须实现
是否必须实现:抽象方法子类必须实现,虚方法子类可以不实现
// 定义抽象类
public abstract class AbstractBase
{
// 收入金额
public decimal InTotal { get; set; }
// 支出金额
public decimal ExTotal { get; set; }
// 收支金额
public decimal InExTotal { get; set; }
// 计算收支金额
public abstract void CallInExAmount();
public abstract void Show();
}
// 定义抽象类的实现类
public class AbstractDay : AbstractBase
{
public string Date{ get; set; }
public override void CallInExAmount()
{
CommonVariable.TotalIncomeAmount = InTotal;
CommonVariable.TotalExpendAmount = ExTotal;
CommonVariable.TotalInExAmount = CommonVariable.TotalIncomeAmount - CommonVariable.TotalExpendAmount;
}
public override void Show()
{
MessageBox.Show($"收入:{InTotal}, 支出:{ExTotal}, 收支{InExTotal}");
}
// 这里抽象类的实现类中,还可以定义其他的方法:
// 普通的方法
public void Init()
{
;
}
// 虚方法
public virtual void ShowInfo()
{
;
}
}
// 调用
AbstractDay abstractDay = new AbstractDay()
{
Date = "2022-10-23",
InTotal = 200,
ExTotal = 100
};
abstractDay.CallInExAmount();
abstractDay.Show();
接口(interface)用来定义一种程序的语法合同。接口定义为”做什么”.
实现接口的类或者结构要与接口的定义严格一致,定义了”怎么做“。
接口可以从多个基接口继承,类或结构可以实现多个接口。
接口可以定义方法、属性、事件。接口不提供它所定义的成员的实现。
接口不能直接被实例化。
接口支持多重继承。
// C#中的接口是独立于类来定义的,接口和类都可以继承多个接口,而类只可以继承一个基类。
interface IMyInterface--接口名称
{
void MethodToImplement(); --方法定义
}
类继承接口后,需要实现接口的方法, 方法名必须与接口定义的方法名一致。
接口继承: 如果一个接口继承其他接口,那么实现类或结构就需要实现所有接口的成员。
// 定义接口
public interface IAct
{
// 接口可以定义属性,但是一般不推荐在接口中定义属性。
// public string Name { get; set; }
bool Add(RecordParent recordParent);
bool Delete(int id);
void Show();
}
// 创建第二个接口,继承第一个接口
public interface IRecord : IAct
{
void Edit(RecordParent recordParent);
}
// 创建第三个接口, 继承第第二个接口,继承第一个接口(可以继承一个接口,也可以继承多个接口)
public interface IStatistics : IRecord, IAct
{
// 定义计算收支的方法。
decimal CallInExTotal();
}
语法:
interface IMyInterface--接口名称(通常以I开头) { • void 方法名([参数列表]); • 方法定义,不允许使用访问修饰符、修饰符、不能定义字段、不能有方法体 }
一般只在接口中定义方法
类继承接口后,需要实现接口的方法, 方法名必须与接口定义的方法名一致。
接口继承: 如果一个接口继承其他接口,那么实现类或结构就需要实现所有接口的成员。
// 接口类实现
public class StatisticsImp : IStatistics
{
public bool Add(RecordParent recordParent)
{
return true;
}
public decimal CallInExTotal()
{
return 3.5M;
}
public bool Delete(int id)
{
throw new NotImplementedException();
}
public string Edit(RecordParent recordParent)
{
return recordParent.Name + "-" + recordParent.Type;
}
public void Show()
{
throw new NotImplementedException();
}
}
// 调用
IStatistics statisticsImp = new StatisticsImp();
RecordParent recordParent = new RecordParent()
{
Name = "ShaXiang",
Type = "工资收入"
};
bool a1 = statisticsImp.Add(recordParent);
string str1 = statisticsImp.Edit(recordParent);
decimal de1 = statisticsImp.CallInExTotal();
相同点:
都可以被继承(实现)
都不能被实例化
都可以包含方法声明
派生类必须实现未实现的方法
区别:
抽象类可以定义字段、属性、方法实现。接口只能定义属性、索引器、事件、和方法声明,不能包含字段。(一般通常只定义方法)
抽象类是一个不完整的类,需要进一步细化,而接口是一个行为规范,规定能“做什么”。
接口可以被多重实现,抽象类只能被单一继承。
抽象类更多的是对一系列紧密相关的类的抽象出来的概念,强调“是什么”;而接口大多数是对众多类型的相同功能的规定,重在行为定义(功能),**强调“做什么”,而不关心怎么做。
两者选择:
如果是关系密切的对象,特征和行为都包含,选择抽象类
如果只是注重行为的抽象,选择接口