C# 基础问题汇集
(1)new List并不是null,可以正常的被遍历和AddRange
class Program
{
public static void Main()
{
//var t = new test();
//t.b = 100;
//Console.WriteLine($"{t.b}");
var a = new List<int> { 1, 2 };
var b = new List<int>();
a.AddRange(b);
foreach (var c in b)
{
Console.WriteLine($"{c}");
}
Console.WriteLine($"{b.Count}");
Console.WriteLine($"{b == null}");
}
}
(2)属性可以被private访问修饰符修饰,但这样就不能被实例从外部访问,一般是public或protected。
class test
{
private int a { get; set; }
public int b { get { return a; } set { a = 100; } }
}
class Program
{
public static void Main()
{
var t = new test();
Console.WriteLine($"{t.a}");
t.b = 100;
Console.WriteLine($"{t.b}");
}
}
(3)for 循环
int a = 1;
for (int i = 1; i < a; i++)
{
Console.WriteLine("h");
}
一次也没运行,for 循环的执行顺序是:
for (Initializer;TestExpr;IterationExpr)
Statement
在for 循环的开始,执行一次Initializer,然后对TestExpr求值,如果它返回true,执行Statement,接着是IterationExpr,然后控制回到循环的顶端,再次对TestExpr求值,依次进行,直到返回false,就继续执行Statement之后的语句。
(4) 虚方法与覆写方法,基类访问
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp1
{
class A
{
public virtual void Print()
{
Console.WriteLine("this is a");
}
}
class B:A
{
public override void Print()
{
Console.WriteLine("this is b");
}
}
class Program
{
public static void Main()
{
B b= new B();
A a = (A)b;
a.Print();
B c = (B)a;
c.Print();
}
}
}
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp1
{
class A
{
public void Print()
{
Console.WriteLine("this is a");
}
}
class B:A
{
new public string Print()
{
Console.WriteLine("this is b");
return "yes";
}
}
class Program
{
public static void Main()
{
B b= new B();
A a = (A)b;
a.Print();
B c = (B)a;
c.Print();
}
}
}
虚方法与覆写方法是成对存在的,并且要求函数除了签名一样,返回类型也一样,在派生类转为基类时,调用转化后的基类的虚方法,调用的是派生类的覆写方法。而使用new来屏蔽基类函数,则不要求返回类型一样,只要签名一样就可以了,并且派生类转为基类后,调用转化后的基类的“被屏蔽的方法”就是基类的“被屏蔽的方法”,而不再是派生类的方法。
(5)虚方法,抽象类,接口对实现多态的一点感想
多态就是:允许将子类类型的指针赋值给父类类型的指针。也就是同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。在运行时,可以通过指向基类的指针,来调用实现派生类中的方法。
- a 首先,我们先来看下怎样用虚方法实现多态
我们都知道,喜鹊(Magpie)、老鹰(Eagle)、企鹅(Penguin)都是属于鸟类,我们可以根据这三者的共有特性提取出鸟类(Bird)做为父类,喜鹊喜欢吃虫子,老鹰喜欢吃肉,企鹅喜欢吃鱼。创建基类Bird如下,添加一个虚方法Eat(),依次对Magpie,Eagle,Penguin添加重载方法。
/// <summary>
/// 鸟类:父类
/// </summary>
public class Bird
{
/// <summary>
/// 吃:虚方法
/// </summary>
public virtual void Eat()
{
Console.WriteLine("我是一只小小鸟,我喜欢吃虫子~");
}
}
最后
static void Main(string[] args)
{
//创建一个Bird基类数组,添加基类Bird对象,Magpie对象,Eagle对象,Penguin对象
Bird[] birds = {
new Bird(),
new Magpie(),
new Eagle(),
new Penguin()
};
//遍历一下birds数组
foreach (Bird bird in birds)
{
bird.Eat();
}
Console.ReadKey();
}
以上程序也体现了开放封闭原则,如果后面的同事需要扩展我这个程序,还想再添加一个猫头鹰(Owl),很容易,只需要添加一个Owl类文件,继承Bird,重写Eat()方法,添加给父类对象就可以了。至此,该程序的扩展性得到了提升,而又不需要查看源代码是如何实现的就可以扩展新功能。这就是多态带来的好处。
- b 来看下利用抽象如何来实现多态
还是刚才的例子,我们发现Bird这个父类,我们根本不需要使用它创建的对象,它存在的意义就是供子类来继承。所以我们可以用抽象类来优化它。
我们把Bird父类改成抽象类,Eat()方法改成抽象方法。代码如下:
/// <summary>
/// 鸟类:基类
/// </summary>
public abstract class Bird
{
/// <summary>
/// 吃:抽象方法
/// </summary>
public abstract void Eat();
}
Main主函数中Bird就不能创建对象了,代码稍微修改如下:
static void Main(string[] args)
{
//创建一个Bird基类数组,添加 Magpie对象,Eagle对象,Penguin对象
Bird[] birds = {
new Magpie(),
new Eagle(),
new Penguin()
};
//遍历一下birds数组
foreach (Bird bird in birds)
{
bird.Eat();
}
Console.ReadKey();
}
由此可见,我们选择使用虚方法实现多态还是抽象类抽象方法实现多态,取决于我们是否需要使用基类实例化的对象.
- c接口实现多态
我要问一个问题,喜鹊和老鹰都可以飞,这个飞的能力,我怎么来实现呢?
XXX答:“在父类Bird中添加一个Fly方法不就好了~~”
我再问:“好的,照你说的,企鹅继承父类Bird,但是不能企鹅不能飞啊,这样在父类Bird中添加Fly方法是不是不合适呢?”
XXX答:“那就在能飞的鸟类中分别添加Fly方法不就可以了吗?”
对,这样是可以,功能完全可以实现,可是这样违背了面向对象开放封闭原则,下次我要再扩展一个鸟类比如猫头鹰(Owl),我还要去源代码中看下Fly是怎么实现的,然后在Owl中再次添加Fly方法,相同的功能,重复的代码,这样是不合理的,程序也不便于扩展;
其次,如果我还要添加一个飞机类(Plane),我继承Bird父类,合适吗?
很显然,不合适!所以我们需要一种规则,那就是接口了,喜鹊,老鹰,飞机,我都实现这个接口,那就可以飞了,而企鹅我不实现这个接口,它就不能飞~~
好,接下来介绍一下接口如何实现多态~
添加一个接口IFlyable,代码如下:
/// <summary>
/// 飞 接口
/// </summary>
public interface IFlyable
{
void Fly();
}
在Main主函数中,创建一个IFlyable接口数组,代码实现如下:
static void Main(string[] args)
{
//创建一个IFlyable接口数组,添加 Magpie对象,Eagle对象
IFlyable[] flys = {
new Magpie(),
new Eagle()
};
//遍历一下flys数组
foreach (IFlyable fly in flys)
{
fly.Fly();
}
Console.ReadKey();
}
所以有人称接口让C#成为多重继承也是有一定道理的。
飞机也能飞,继承Bird不合适的问题,现在有了接口,这个问题也可以解决了。如下,我添加一个飞机Plane类,实现IFlyable接口,代码如下:
/// <summary>
/// 飞机类,实现IFlyable接口
/// </summary>
public class Plane:IFlyable
{
/// <summary>
/// 实现接口方法
/// </summary>
public void Fly()
{
Console.WriteLine("我是一架飞机,我也能飞~~");
}
}
在Main主函数中,接口IFlyable数组,添加Plane对象:
class Program
{
static void Main(string[] args)
{
//创建一个IFlyable接口数组,添加 Magpie对象,Eagle对象,Plane对象
IFlyable[] flys = {
new Magpie(),
new Eagle(),
new Plane()
};
//遍历一下flys数组
foreach (IFlyable fly in flys)
{
fly.Fly();
}
Console.ReadKey();
}
}
(6)as 和is关键字
C# 提供 is 和 as 运算符来进行转换。可以使用这两个运算符来测试强制转换是否会成功,而没有引发异常的风险。
is 运算符检查对象是否与给定类型兼容,重点是检查,只是检查.
eg:if(b is B)将检查对象b 是否为B类型的一个实例,或者是从B派生的一个类型的实例
as 运算符用于在可兼容的引用类型之间执行类似于强制类型转换的操作,重点是操作。与强转不同的是,当转换失败时,as 运算符将返回NULL空,而不是引发异常。
(7) static void Main(string[] args)
string[]args:这是用来接收命令行传入的参数。string[]是声明args的数据类型,可以存储字符串数组。在Visual studio中,可以右键项目--属性--调试---应用程序参数来进行设置。
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp
{
struct Simple
{
public int X;
public int Y;
}
class SimpleClass
{
public int X;
public int Y;
}
class test { }
class Program
{
public static void Main(string[] args)
{
foreach (string s in args)
{
Console.WriteLine(s);
}
}
}
}
(8)不是只声明的对象不能用,是只声明的局部变量不可以用,只声明的字段如果是引用类型,那么它就是null。
如上图所示,由于在Main
入口函数中,声明变量都是局部变量,所以不赋值,就不可使用,也不可拿来与null
比较。
相反,在类中只声明的字段,而不赋值,那么引用类型默认就是null,值类型就是相应的默认值,如上图。
(9) 类内定义的委托和类,在外部归类所有,而非实例所有
class test
{
public class test1 { }
public delegate void test2();
}
class Program
{
public static void test3() { }
public static void Main(string[] args)
{
var aa = new test();
var bb = new test.test1();
var cc = new test.test2(test3);
}
(10)重载Equals方法,与==
不同的是,==
对于值类型只检查是否值一样,对于引用类型,只检查引用地址是否一样。也要注意到private限定的是只有在本类内部才能被看到
public class test
{
private int data;
public test(int data)
{
this.data = data;
}
//public bool Equals(test rhs)
//{
// return this.data == rhs.data;
//}
}
class Program
{
public static void Main()
{
var a = new test(10);
var b = new test(9);
var c = new test(10);
Console.WriteLine(a.Equals(b));
Console.WriteLine(a.Equals(c));
}
}
out:
False
False
重载Equals()
方法
public class test
{
private int data;
public test(int data)
{
this.data = data;
}
public bool Equals(test rhs)
{
return this.data == rhs.data; // data被限定为private,但在本类内部,依然可以用传过来的rhs实例来访问private data.
}
}
class Program
{
public static void Main()
{
var a = new test(10);
var b = new test(9);
var c = new test(10);
Console.WriteLine(a.Equals(b));
Console.WriteLine(a.Equals(c));
}
}
out:
False
True
(11)C#嵌套类
1.基本概念
嵌套类:在一个类中定义另一个类,分为静态嵌套类(使用少)和非静态嵌套类(又称内部类)。
内部类: (1) 在一个类中直接定义类。
(2) 在一个方法中定义类。
(3) 匿名内部类。
-
嵌套类的使用
(1)外部类只能够访问嵌套类中修饰符为public、internal的字段、方法、属性。示例如下
public class Animal
{
static void Set_Animal(){ //编译正确 Monkey._high = 123; Monkey._age = 4; //编译失败 Monkey._name = "hoho"; } private class Monkey { private static string _name; protected string _sex; internal static int _age; public static int _high; public Monkey() { } public void show() { } }
}
(2)嵌套类可以访问外部类的方法、属性、字段而不受访问修饰符的限制
public class Animal
{
private static int _Age;
private string name;
private static void DoSomething()
{
Console.WriteLine(_Age);
}
private void DoStr()
{
monkey.method(this);
}
/*嵌套类 定义*/
class monkey
{
public static void method(Animal a)
{
_Age = 23; //静态成员
a.name = "test"; //实例成员
}
}
}
-
嵌套类访问外部类实例的方法、字段、属性时候。一般在采取构造函数输入外部类
public class Animal
{
private string _name;
//*嵌套类 定义*/
class monkey
{
public monkey(Animal a)
{
a._name = "test";
}
}
}
4.继承类可以再定义一个内嵌类并从继承父类中嵌套类
public class Animal
{
//*嵌套类 定义*/
protected class monkey
{
public virtual void Method()
{
Console.WriteLine("monkey");
}
}
}
public class S_Animal : Animal
{
class s_monkey :monkey
{
public override void Method()
{
Console.WriteLine("s_monkey");
}
}
}注:因为S_Animal 继承于Animal ,因此s_monkey可以继承monkey类,从而获取重写父嵌套类的机会。但是monkey必须是可继承类及可访问的(非private 、sealed、static)。
嵌套类可以随意外部类的任何数据属性,而外部类访问嵌套类就只能遵守访问修饰符。从这个角度看,嵌套类是外部类的补充,通过嵌套类可以获取更好的封装性,增加外部类的可维护性和可读性。
从程序结构看,嵌套类在逻辑上更加接近使用类。可以更有效地表示类与类之间的紧密程度。为类管理提供除命名空间外的另一种方法。
5.单例模式(singleton)就采用一种为 延迟加载初始化实例 的方法如下:
public class Animal
{
public Animal(){}
public static Animal Intance
{
get { return monkey._intance; }
}
class monkey
{
public readonly static Animal _intance = new Animal();
}
}注: 延迟加载 嵌套类的静态构造函数不会随着外部类的触发而初始化。因此可以有效地避免创建时候初始化时间,当需要使用内嵌类的时候,嵌套类才开始初始化。
6.反射内嵌类需要使用"+"而不是我们常使用的"." 。
namespace AnimalClass
{
public class Animal
{
public class monkey
{
protected void Method()
{
//do something...
}
}
}
}
注://成功
object o1 = System.Activator.CreateInstance("AnimalClass", "AnimalClass.Animal+monkey");
//失败 抛出System.TypeLoadException 异常
object o2 = System.Activator.CreateInstance("AnimalClass", "AnimalClass.Animal.monkey");
(12)null也可以进行类型转换
string s = null;
object ss = (object)s;
string sss = (string)ss;
if (sss == null) Console.WriteLine("yes");
控制台显示“yes"