单独记录一下毕业后这两年所经历的一些关于C#的面试。就当梳理一下知识。
吾尝终日而思矣,不如须臾之所学也;吾尝跂而望矣,不如登高之博见也。登高而招,臂非加长也,而见者远;顺风而呼,声非加疾也,而闻者彰。假舆马者,非利足也,而致千里;假舟楫者,非能水也,而绝江河。君子生非异也,善假于物也。–荀子《劝学》
做这个行业,个人认为最重要的就是要“善假于物也”,多学习别人优秀的写法。
MVC(Model View Controller)模型-视图-控制器
首先回答一些套话:
模型:模型负责业务逻辑操作和数据库操作。它专注于用户根本业务问题的解决与实现,而不关心用户结果的展示,就是说模型与数据格式无关,这样一个模型就可以为多个视图提供数据。
视图:是用户看到并与之交户的界面,用户通过视图实现他要达到的目的,但视图本身并不会实现用户的需求。视图所关心的是如何通过不同方式将数据展示给用户,并有条件允许用户操作,而不关心展示的是什么.
控制器: 获取View的请求 调用模型将数据交给视图进行展示
在这个过程中,控制器其实只是起到了承上启下的作用,它只负责中转(指挥调度),不负责具体的业务操作。
MVC最大的好处是将逻辑和页面分离。
但是我们的目的是为了找工作而不是为了骗人。要从事这个职业,MVC模式还是需要掌握的。
回答这个问题重点就是要弄清楚,不同功能的代码放在哪些不同的文件里面。要用自己的话举个例子去解释一下。在winform中,一般来说,都是先写model文件,model文件是view中的映射,如果view中需要显示的属性,model中要有,需要执行的方法,model中有对应的事件。然后写controller文件,controller中需要有待操作的view和与之对应的model,进行实例化,然后主要进行逻辑代码的编写。view中需要有之对应的controller的实例,这样才能方便调用controller中的逻辑方法。
model里面
public class Person : INotifyPropertyChanged
{
private string _id;
public string ID
{
get { return _id; }
set { _id = value; OnPropertyChanged("ID"); }
}
#region INotifyPropertyChanged 成员
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string PropertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(PropertyName));
}
}
#endregion
}
control里面
public class PersonControllor
{
public PersonForm View;
public Person Model;
public PersonControllor(PersonForm view)
{
//初始化了一个Model
Model = new Person() { ID = "1", Name = "xiaojun" };
//通过构造函数将View注入到Controllor中
this.View = view;
//建立起View 和Controllor的关联
//这时候View中能使用它所对应的Controllor进行业务逻辑的操作,Model也能和VIEW UI控件进行双向绑定
this.View.Controllor = this;
}
///
/// 执行一个业务逻辑
///
public void UpdatePerson()
{
UpdateToDataBase(Model);
}
private void UpdateToDataBase(Person p)
{
//do some thing
//执行将数据插入到数据库的操作
System.Windows.Forms.MessageBox.Show("ID:" + p.ID + " Name:" + p.Name);
}
//这边的业务逻辑应该写在model里比较合适
}
view里面
private PersonControllor _controllor;
public PersonControllor Controllor
{
get { return _controllor; }
set
{
this._controllor = value;
//绑定一定只能写在给Controllor赋值以后而不能写在PersonForm的构造函数中(此时Controllor还未被实例化)
//因为我们这里采用的是Controllor-First而不是View-First,不然Controllor.Model为null会异常
//将View通过构造函数注入到Controllor中的属于Controllor-First,这时候Controllor先创建
//将Controllor通过构造函数注入到View中的属于View-First,这时候View先创建
this.textBox1.DataBindings.Add("Text", Controllor.Model, "ID");
this.textBox2.DataBindings.Add("Text", Controllor.Model, "Name");
}
}
private void button1_Click(object sender, EventArgs e)
{
//改变VIEW的UI控件的值,Controllor的Model会跟着变
this.textBox1.Text = "2";
this.textBox2.Text = "jacky";
Controllor.UpdatePerson();
}
mainform里面
//先创建Controllor(PersonControllor)再将View(PersonForm)注入到Controllor(PersonControllor)中
PersonControllor controllor = new PersonControllor(new PersonForm());
同理 MVVM模式 不是光说说每个页面是干什么的就可以
Viewmodel,继承INotifyPropertyChanged,当ViewModel的属性发生改变,即执行了set访问器时,触发PropertyChanged事件,通知前端控件即时更新绑定的属性值;
这些textbox,checkbox,combobox的tag设置为对应student对象的属性名
通过找到窗体内所有控件,如果tag属性与studnet的属性相同,则把控件与student对象属性绑定
MVVM和MVC的区别
MVC和MVVM的区别并不是VM完全取代了C,ViewModel存在目的在于抽离Controller中展示的业务逻辑,而不是替代Controller,其它视图操作业务等还是应该放在Controller中实现。也就是说MVVM实现的是业务逻辑组件的重用。
mvvm 实现了v的双向绑定
mvc后端开发思想 mvvm前端开发思想
它实现了View和Model的自动同步,也就是当Model的属性改变时,我们不用再自己手动操作Dom元素,来改变View的显示,而是改变属性后该属性对应View层显示会自动改变。
关键字是Attribute,特性就是关联了一个目标对象的一段配置信息,本质上是一个类,其为目标元素提供关联附加信息。运行期以反射的方式来获取附加信息。
自定义特性是定义一个继承自System.Attribute类的类型
**获取公共属性GetType().GetProperties() GetCustomAttributes **
获知一个元素是否申明了某个特性用 System.Attribute.GetCustomerAttributes方法
程序集包含模块,而模块又包括类型,类型下有成员,反射就是管理程序集,模块,类型的对象,它能够动态的创建类型的实例,设置现有对象的类型或者获取现有对象的类型,能调用类型的方法和访问类型的字段属性。反射最大的好处是在运行时创建和使用类型实例。
System.Reflection命名空间之下
委托是将一种方法作为参数代入到另一种方法。委托的本质是一个类,和类平级,也能放在类里面,但是不能放在方法里面,是对一类方法的抽象。
触发委托有2种方式: 委托实例.Invoke(参数列表),委托实例(参数列表),直接加参数是Invoke(参数)的一个捷径,其实等价调用 Invoke();
事件是委托的实例,是一种对委托的应用。 事件时属于类的成员,要放在类的内部。
通过+=为事件注册多个委托实例或多个方法
比如:onclick事件中的参数就是一种方法。
跨类的话一般是用委托和事件的组合,我一开始接触的就是需要跨类的,所以一直都认为委托和事件是组合使用的。其实如果不需要跨类的话,委托或者事件都是为了使代码更加简洁,你不使用委托或者事件,直接调用方法也能达到相同的效果,但是代码就会比较冗余,会有重复代码
需要跨类调用别的方法时候,这种写法(如下面代码)虽然简单,但是 管他黑猫,白猫,能抓到老鼠的就是好猫
using System;
namespace LearningDelegate
{
class Program
{
static void Main(string[] args)
{
//出版社有一本叫《故事会》的杂志
Publisher publisher = new Publisher("《故事会》");
//读者力力a订了这本杂志
Observer observerA = new Observer("力力a");
publisher.Magazine += observerA.RecieveMagazine;
//读者力力b也订了这本杂志
Observer observerB = new Observer("力力b");
publisher.Magazine += observerB.RecieveMagazine;
//出版社印刷本月的《故事会》
publisher.PublishingMagazine();
Console.ReadLine();
}
}
//读者
class Observer
{
public Observer(string _name)
{
name = _name;
}
public string name;
public void RecieveMagazine(string message)
{
Console.WriteLine("{0}收到了{1}, 仔细阅读了一番。", this.name, message);
}
}
//出版社
class Publisher
{
public Publisher(string megName)
{
_magazineName = megName;
}
private string _magazineName = string.Empty;
public string magazineName
{
//这种get set写法,和正常用花括号的写法效果一样,代码这些风格习惯,见到有更好的就直接用,善假于物。
//但是如果set get中除了赋值还有一些其他的操作,例如判断条件之类的,只能用花括号写法。
get=>_magazineName;
set=>_magazineName=value;
}
//这里就是使用了delegate,event的组合使用
public delegate void MagazineDelegate(string message);
//使用event关键字 还可以有一个封装效果,如果没有event 代码也可以正常运行,效果也相同,但是会有隐患
public event MagazineDelegate Magazine;
public void PublishingMagazine()
{
//如果没人订,就不用印了
//此处必须判断事件对象是否为空
if (Magazine != null)
{
Magazine(magazineName);
//或者另一种调用方式
//Invoke 与begininvoke区别在于,invoke会阻塞当前线程,直到invoke调用结束,才会继续执行下去,
//而begininvoke 则可以异步进行调用,也就是该方法封送完毕后马上返回,不会等待委托方法的执行结束,
//调用者线程将不会被阻塞。
//但是调用者也可以使用EndInvoke方法或者其它类似WaitHandle机制等待异步操作的完成。
//Magazine.Invoke(magazineName);
}
}
}
}
后来在项目中,由于每个人个人习惯不同,又接触到了另外一种写法,了解了之后感觉 确实另一种写法更加标准。
但是吧,道理我都懂,用不用就是另外一回事了,毕竟只要能抓到老鼠就行
using System;
namespace LearningDelegate
{
class Program
{
static void Main(string[] args)
{
//出版社有一本叫《故事会》的杂志
Publisher publisher = new Publisher("《故事会》");
//读者力力a订了这本杂志
Observer observerA = new Observer("力力a");
publisher.Magazine += observerA.RecieveMagazine;
//读者力力b也订了这本杂志
Observer observerB = new Observer("力力b");
publisher.Magazine += observerB.RecieveMagazine;
//出版社印刷本月的《故事会》
publisher.PublishingMagazine();
Console.ReadLine();
}
}
//读者
class Observer
{
public Observer(string _name)
{
name = _name;
}
public string name;
//接受信息的函数要与Event的格式保持一致,输入一个object对象和Event消息类
public void RecieveMagazine(object sender, Publisher.MagazineMessage magazineMessage)
{
Console.WriteLine("{0}收到了{1}, 仔细阅读了一番。", this.name, magazineMessage.Message);
}
}
//出版社
class Publisher
{
//事件传递的消息必须封装到一个类中,该类必须继承EventArgs类
public class MagazineMessageEventArgs : EventArgs
{
public MagazineMessageEventArgs(string mes)
{
message = mes;
}
private string message;
public string Message { get => message; set => message = value; }
}
public Publisher(string megName)
{
magazineName = megName;
}
public string magazineName;
//定义Event
public event EventHandler<MagazineMessageEventArgs> Magazine;
//EventHandler 的原型是一个泛型委托:
//delegate void EventHandler(object sender, TEventArgs e)
//调用Event,Event只能在自己定义类中被触发调用
public void PublishingMagazine()
{
//与Delegate一样,此处必须判断event对象是否为空,调用空的Event/Delegate对象会引发异常
if (Magazine != null)
{
Magazine(this, new MagazineMessageEventArgs(magazineName));
}
}
}
}
不经过CLR运行,程序员自行分配和释放内存空间
当一个代码块使用 unsafe 修饰符标记时,C# 允许在函数中使用指针变量。不安全代码或非托管代码是指使用了指针变量的代码块。c#是托管代码。
在同一个声明中声明多个指针时,星号 * 仅与基础类型一起写入;而不是用作每个指针名称的前缀。 例如:
int* p1, p2, p3; // 正确
int *p1, *p2, *p3; // 错误
注意事项 你不能用var关键字声明一个变量而不给它赋值
匿名类型
var obj = new {Guid.Empty, myTitle = “匿名类型”, myOtherParam = new int[] { 1, 2, 3, 4 } };
Console.WriteLine(obj.Empty);//另一个对象的属性名字,被原封不动的拷贝到匿名对象中来了。
Console.WriteLine(obj.myTitle);
不要试图在创建匿名对象的方法外面去访问对象的属性
自动属性
public class MyObj
{
public Guid id { get; set; } //不需要花括号去写
public string Title { get; set; }
}
// The Three Parts of a LINQ Query:
// 1. Data source.
int[] numbers = new int[7] { 0, 1, 2, 3, 4, 5, 6 };
// 2. Query creation.
// numQuery is an IEnumerable
var numQuery =
from num in numbers
where (num % 2) == 0
select num;
var queryLondonCustomers3 =
from cust in customers
where cust.City == “London”
orderby cust.Name ascending(升)descending(降)
select cust;
执行主要和次要排序
IEnumerable query = from word in words
orderby word.Length, word.Substring(0, 1)
select word;
返回的序列只包含位于第一个输入序列但不位于第二个输入序列的元素
IEnumerable query = from planet in planets1.Except(planets2)
select planet;
返回的序列包含两个输入序列共有的元素
IEnumerable query = from planet in planets1.Intersect(planets2)
select planet;
返回的序列包含两个输入序列的唯一元素
IEnumerable query = from planet in planets1.Union(planets2)
select planet;
使用 join … in … on … equals … 子句基于特定值联接两个序列
var query = from product in products
join category in categories on product.CategoryId equals category.Id
select new { product.Name, category.CategoryName };
//通过匿名委托创建
Thread thread1 = new Thread(delegate() { Console.WriteLine(“我是通过匿名委托创建的线程”); });
thread1.Start();
//通过Lambda表达式创建
Thread thread2 = new Thread(() => Console.WriteLine(“我是通过Lambda表达式创建的委托”));
thread2.Start();
扩展方法所在的类必须声明为static。
扩展方法必须声明为public和static。
扩展方法的第一个参数必须包含关键字this,并且在后面指定扩展的类的名称。
扩展方法被定义为静态方法,但它们是通过实例方法语法进行调用的
如果扩展的方法与扩展的类型中具有相同的签名,那么方法永远不会被调用
看到反编译成IL之后发现两者并无不同
扩展方法本质上就是静态方法,之所以出现扩展方法是C#以另外一种形式表现静态方法而已
扩展方法能够被继承
不能有其他参数修饰第一个参数(如ref,out等等)
使用Interlocked进行原子操作“为多个线程共享的变量提供原子操作”
CompareExchange()
安全比较两个值是不是相等。如果相等,将第三个值于其中一个值交换
Decrement()
安全递减1,相当于 i–
Exchange()
安全交换数据,相当于 a = 30
Increment()
安全递加1,相当于 i++
Add()
安全相加一个数值,相当于 a = a + 3
Read()
安全读取数值,相等于int a=b
Interlocked.Increment(ref counter);
using system.Drawing命名空间
Graphics类封装了绘制直线、曲线、图形、图像和文本的方法,是GDI+实现绘制直线、曲线、图形、图像和文本的类。是进行一切GDI+操作的基础类。
主要有Graphics类、Bitmap类、从Brush类继承的类、Font类、Icon类、Image类、Pen类、Color类等
简单步骤:
准备一个画板
创建一个画板主要有3种方式:
A: 在窗体或控件的Paint事件中直接引用Graphics对象
private void Form1_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics; //创建画板,这里的画板是由Form提供的.
}
B: 利用窗体或某个控件的CreateGraphics方法
using (Graphics g = panel1.CreateGraphics())
C: 从继承自图像的任何对象创建Graphics对象
Bitmap CatchBmp = new Bitmap(Screen.AllScreens[0].Bounds.Width, Screen.AllScreens[0].Bounds.Height);
Graphics g = Graphics.FromImage(CatchBmp);
准备一个笔
Pen的属性主要有: Color(颜色),DashCap(短划线终点形状),DashStyle(虚线样式),EndCap(线尾形状), StartCap(线头形状),Width(粗细)等.
Pen p = new Pen(Color.Blue, 5);//设置笔的粗细为,颜色为蓝色
Graphics g = this.CreateGraphics();
//画虚线
p.DashStyle = DashStyle.Dot;//定义虚线的样式为点
g.DrawLine(p, 10, 10, 200, 10);
//自定义虚线
p.DashPattern = new float[] { 5, 1 };//设置短划线和空白部分的数组 5为虚线长度,1为虚线间距
g.DrawLine(p, new Point(10, 20), new Point(20, 200));
//画箭头,只对不封闭曲线有用
p.DashStyle = DashStyle.Solid;//实线
p.EndCap = LineCap.ArrowAnchor;//定义线尾的样式为箭头
g.DrawLine(p, 10, 30, 200, 30);
g.Dispose();
p.Dispose();
注意:在winform中的坐标轴和我们平时接触的平面直角坐标轴不同,winform中的坐标轴方向完全相反:窗体的左上角为原点(0,0),水平向左则X增大,垂直下向则Y增大
//设置控件样式为双缓冲,以有效减少图片闪烁
this.SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint, true);
this.UpdateStyles();
UserPaint
如果为 true,控件将自行绘制,而不是通过操作系统来绘制。 如果为 false,将不会引发 System.Windows.Forms.Control.Paint事件。 此样式仅适用于派生自 System.Windows.Forms.Control 的类。
ResizeRedraw
如果为 true,则在调整控件大小时重绘控件。
AllPaintingInWmPaint
如果为 true,控件将忽略 WM_ERASEBKGND 窗口消息以减少闪烁。 仅当 System.Windows.Forms.ControlStyles.UserPaint
设置为 true 时,才应当应用该样式。
OptimizedDoubleBuffer
如果为 true,则该控件首先在缓冲区中绘制,而不是直接绘制到屏幕上,这样可以减少闪烁。 如果将此属性设置为 true,则还应当将 System.Windows.Forms.ControlStyles.AllPaintingInWmPaint设置为 true。
.Net所指的托管只是针对内存这一个方面,并不是对于所有的资源;因此对于Stream,数据库的连接,GDI+的相关对象,还有Com对象等等,这些资源并不是受到.Net管理而统称为非托管资源。而对于内存的释放和回收,系统提供了GC-Garbage Collector,而至于其他资源则需要手动进行释放。
一个引用类型对象所占用的内存需要被GC回收,需要先成为垃圾。那么.Net如何判定一个引用类型对象是垃圾呢,.Net的判断很简单,只要判定此对象或者其包含的子对象没有任何引用是有效的,那么系统就认为它是垃圾。
可以知道析构函数只能被GC来调用的,那么无法确定它什么时候被调用,因此用它作为资源的释放并不是很合理,因为资源释放不及时;但是为了防止资源泄漏,毕竟它会被GC调用,因此析构函数可以作为一个补救方法。而Close与Dispose这两种方法的区别在于,调用完了对象的Close方法后,此对象有可能被重新进行使用;而Dispose方法来说,此对象所占有的资源需要被标记为无用了,也就是此对象被销毁了,不能再被使用
以通过调用GC.Collect();来强制GC进行垃圾回收
值传递时,系统首先为被调用方法的形参分配内存空间,并将实参的值复制给形参,此后,被调用方法中形参值得任何改变都不会影响到相应的实参。
引用传递时,系统不是将实参本身的值复制后传递给形参,而是将其引用值(即地址值)传递给形参,因此,形参所引用的该地址上的变量与传递的实参相同,方法体内相应形参值得任何改变都将影响到作为引用传递的实参。
简而言之,按值传递传递的是值的副本,会在栈上多创建一个相同的变量。而引用传递传递的是地址,方法内操作的是同一个变量。
只凭这个是区分不开高级程序员和普通程序员的 下面的几个问题就是对这个问题的补充
虽然值传递不会影响到相应的实参,但是可以通过 ref 和 out 来决定参数是否按照引用传递。
(1)ref指定的参数在函数调用时必须先初始化,而out不用
(2)out指定的参数在进入函数时会清空自己,因此必须在函数内部进行初始化赋值操作,而ref不用
总结:ref可以把值传到方法里,也可以把值传到方法外;out只可以把值传到方法外
注意:string作为特殊的引用类型,其操作是与值类型看齐的,若要将方法内对形参赋值后的结果传递出来,需要加上ref或out关键字
值类型:struct、enum、int、float、char、bool、decimal
引用类型:class、delegate、interface、array、object、string
注意:所有的数据类型的基类都是object,所有控件的基类都是control,使用引用类型的时候,一般是对指针进行的操作而非引用类型对象本身。但是值类型则操作其本身
class可以被实例化,属于引用类型,
class可以实现接口和单继承其他类,还可以作为基类型,是分配在内存的堆上的
struct属于值类型,不能作为基类型,但是可以实现接口,是分配在内存的栈上的.
栈:由编译器自动分配、释放。在函数体中定义的变量通常在栈上。
堆:一般由程序员分配释放。用new、malloc等分配内存函数分配得到的就是在堆上。
栈:是一片连续的内存域,存放在栈中时要管存储顺序,先进后出的原则,有系统自动分配和维护;
堆:是无序的,他是一片不连续的内存域,有用户自己来控制和释放,如果用户自己不释放的话,当内存达到一定的特定值时,通过垃圾回收器(GC)来回收。
1)值类型:值类型总是分配在它声明的地方,作为局部变量时,存储在栈上;作为类对象的字段时,则跟随此对象存储在堆中。
2)引用类型:引用类型存储在堆中。类型实例化的时候,会在堆中开辟一部分空间存储类的实例。类对象的引用(地址)还是存储在栈中。
int?为可空类型,默认值可以是null
int默认值是0
int?是通过int装箱为引用类型实现
装箱:把值类型转换成引用类型
拆箱:把引用类型转换成值类型
在装箱时是不需要显式的类型转换的,不过拆箱需要显式的类型转换。
as在转换的同时判断兼容性,如果无法进行转换,返回位null(没有产生新的对象),as转换是否成功判断的依据是是否位null
is只是做类型兼容性判断,并不执行真正的类型转换,返回true或false,对象为null也会返回false。
as比is效率更高,as只需要做一次类型兼容检查
封装:根据职责将属性和方法封装到一个抽象的类中。外界使用类创建对象,然后让对象调用方法。.对象方法的细节都被封装在类的内部
继承:子类拥有父类的所有属性和方法,目的是实现代码的重用。
多态:不同的子类对象调用相同的方法,产生不同的执行结果。
封装
一个 访问修饰符 定义了一个类成员的范围和可见性。C# 支持的访问修饰符如下所示:
public:所有对象都可以访问;
private:对象本身在对象内部可以访问;
protected:只有该类对象及其子类对象可以访问
internal:同一个程序集的对象可以访问;
protected internal:访问限于当前程序集或派生自包含类的类型。
继承
C# 不支持多重继承。但是,使用接口可以实现多重继承。
多态
C#语言中体现多态有三种方式:虚方法,抽象类, 接口。
抽象类用关键字 abstract 声明抽象类和抽象方法,一个类中有抽象方法,那么这个类就是抽象类了。
抽象方法,就是不含主体(不提供实现方法),例子 public abstract bool Refine(bool isok);
抽象类和抽象方法必须由继承者重写。因此,抽象类不可实例化,只能通过继承被子类重写。
接口用关键字 interface 声明接口,只提供一些方法规约,不提供任何实现,方法不能用public、abstract等修饰,无字段、常量,无构造函数
两者区别:
1.interface中不能有字段,而abstract class可以有;
2.interface中不能有public等修饰符,而abstract class 可以有。
3.interface 可以实现多继承
虚函数:没有实现的,可以由子类继承并重写的函数。
在父类中使用 virtual 关键字修饰的方法, 就是虚方法。在子类中可以使用 override 关键字对该虚方法进行重写。
抽象函数:规定其非虚子类必须实现的函数,必须被重写。
使用关键字 abstract
overload 重载是方法的名称相同。参数或参数类型不同,进行多次重载以适应不同的需要
override 重写是进行基类中函数的重写。实现多态。
重载是面向过程的概念。
重写是面向对象的概念。
对类有意义的字段和方法使用static关键字修饰,称为静态成员,通过类名加访问操作符“.”进行访问;
对类的实例有意义的字段和方法不加static关键字,称为非静态成员或实例成员。
静态字段在内存中只有一个拷贝,非静态字段则是在每个实例对象中拥有一个拷贝。
而方法无论是否为静态,在内存中只会有一份拷贝,区别只是通过类名来访问还是通过实例名来访问。
最先被执行的构造函数,且在一个类里只允许有一个无参的静态构造函数
执行顺序:静态变量>静态构造函数>实例变量>实例构造函数
i++是先赋值,然后再自增;++i是先自增,后赋值。
&是位运算,返回结果是int类型
&&是逻辑运算,返回结果是bool类型
&&具有短路功能,就是如果判断 条件一&&条件二,条件一错误的话,就会不执行条件二的语句
1.异步编程就是在方法调用后立即返回,不会阻塞后续代码执行。
2.异步代码执行完毕后一般会通过回调的形式调用指定的方法,从而完成异步代码块与主代码块(主线程)的通讯。
4.消除需要长时间执行的代码块阻塞整个代码的执行。
5.延迟执行一段代码。
对于异步编程,只要掌握异步读取一个文件,异步读取结束后,通过回调的形式调用指定的方法,例如打印出文件的内容,就基本上足够解决大多数情况了
.NET提供了非常直接的控制线程类型的类型:System.Threading.Thread类。使用该类型可以直观地创建、控制和结束线程。
多线程没接触之前一直觉得很难,接触了之后其实比较简单。将线程的 IsBackground 属性被设为true,就是后台线程。如果利用线程个数过多,还需要设置线程池。
System.Threading.ThreadPool.SetMinThreads(50, 50);
lock关键字是我们在运行多线程的时候,会遇到一些需要线程同步的情况,最通俗易懂的例子就是存钱,取钱操作都会影响你的余额,那么对余额这个属性就需要用lock,就会避免出现错误情况。
Func是有返回值的委托,Action是没有返回值的委托。
运算符:创建对象实例
修饰符:在派生类定
运算符:创建对象实例
修饰符:在派生类定义一个重名的方法,隐藏掉基类方法
约束:泛型约束定义,约束可使用的泛型类型
需要实现IEnumerable接口或声明GetEnumerator方法的类型。
Connection:主要是开启程序和数据库之间的连接。没有利用连接对象将数据库打开,是无法从数据库中取得数据的。Close和Dispose的区别,Close以后还可以Open,Dispose以后则不能再用。
Command:主要可以用来对数据库发出一些指令,例如可以对数据库下达查询、新增、修改、删除数据等指令,以及调用存在数据库中的存储过程等。这个对象是架构在Connection 对象上,也就是Command: 对象是通过在Connection对象连接到数据源。
DataAdapter:主要是在数据源以及DataSet 之间执行数据传输的工作,它可以透过Command 对象下达命令后,并将取得的数据放入DataSet 对象中。这个对象是架构在Command对象上,并提供了许多配合DataSet 使用的功能。
DataSet:这个对象可以视为一个暂存区(Cache),可以把从数据库中所查询到的数据保留起来,甚至可以将整个数据库显示出来,DataSet是放在内存中的。DataSet 的能力不只是可以储存多个Table 而已,还可以透过DataAdapter对象取得一些例如主键等的数据表结构,并可以记录数据表间的关联。DataSet 对象可以说是ADO.NET 中重量级的对象,这个对象架构在DataAdapter对象上,本身不具备和数据源沟通的能力;也就是说我们是将DataAdapter对象当做DataSet 对象以及数据源间传输数据的桥梁。DataSet包含若干DataTable、DataTableTable包含若干DataRow。
DataReader:当我们只需要循序的读取数据而不需要其它操作时,可以使用DataReader 对象。DataReader对象只是一次一次向下循序的读取数据源中的数据,这些数据是存在数据库服务器中的,而不是一次性加载到程序的内存中的,只能(通过游标)读取当前行的数据,而且这些数据是只读的,并不允许作其它的操作。因为DataReader 在读取数据的时候限制了每次只读取一条,而且只能只读,所以使用起来不但节省资源而且效率很好。使用DataReader 对象除了效率较好之外,因为不用把数据全部传回,故可以降低网络的负载。
进程
一个在内存中运行的应用程序。每个进程都有自己独立的一块内存空间,一个进程可以有多个线程,比如在Windows系统中,一个运行的xx.exe就是一个进程。
线程
进程中的一个执行任务(控制单元),负责当前进程中程序的执行。一个进程至少有一个线程,一个进程可以运行多个线程,多个线程可共享数据。
与进程不同的是同类的多个线程共享进程的堆和方法区资源,但每个线程有自己的程序计数器、虚拟机栈和本地方法栈,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
进程与线程的区别总结
线程具有许多传统进程所具有的特征,故又称为轻型进程(Light—Weight Process)或进程元;而把传统的进程称为重型进程(Heavy—Weight Process),它相当于只有一个线程的任务。在引入了线程的操作系统中,通常一个进程都有若干个线程,至少包含一个线程。
根本区别:进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位
资源开销:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。
包含关系:如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。
内存分配:同一进程的线程共享本进程的地址空间和资源,而进程之间的地址空间和资源是相互独立的
影响关系:一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。
执行过程:每个独立的进程有程序运行的入口、顺序执行序列和程序出口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制,两者均可并发执行
通信机制:正因为进程之间互不干扰,相互独立,进程的通信机制相对很复杂,譬如管道,信号,消息队列,共享内存,套接字等通信机制,而线程由于共享数据段所以通信机制很方便。
答.1.使用QueryString,如…?id=1; response. Redirect()…2.使用Session变量
3.使用Server.Transfer
4.使用Applic ation
5.使用Cache
6使用HttpContext的ltem属性
7.使用文件
8.使用数据库
9.使用Cookie
foreach (System.Windows.Forms.Control control in this.Controls)
{
if (control is System.Windows.Forms.TextBox)
{
system.Windows.Forms.TextBox tb = (System.Windows.Forms.TextBox)control ;
tb.Text = String.Empty ;
}
}
答:CTS:通用语言系统。CLS:通用语言规范。CLR:公共语言运行库。
window系统是一个消息驱动的系统, windows操作系统本身有自己的消息队列,消息循环,它捕捉键盘,鼠标的动作生成消息,并将这个消息传给应用程序的消息队列。 余下的工作有应用程序处理消息。
公共属性
HWnd 获取或设定消息的处理函数
Msg 获取或设定消息的ID号
Lparam 指定消息的LParam字段
Wparam 指定消息的WParam字段
Result 指定为响应消息处理函数而向OS系统返回的值
protected override void WndProc(ref Message m)
{
if (m.Msg == 0x0112 && m.WParam == (IntPtr)0xF012) //响应到窗体移动事件
draw = false;
else if (m.Msg == 0x000F && (!draw)) //绘图事件
{
return;
}
else if (m.Msg == 0x0014) // 禁掉清除背景消息
{
return;
}
base.WndProc(ref m);
}
C#对消息重新进行了面对对象的封装,在C#中消息被封装成了事件。
System.Windows.Forms.Application类具有用于启动和停止应用程序和线程以及处理Windows消息的方法。
调用Run以启动当前线程上的应用程序消息循环,并可以选择使其窗体可见。
调用Exit或ExitThread来停止消息循环。
C#中用Application类来处理消息的接收和发送的。消息的循环是由它负责的。
char
char是定长的,这个怎么说呢,比如你用char(10),当你输入6个字符时,它会用英文的空格给补全。当你输入的15个字符时,它会自动截取前10个字符。取值范围1-8000
优点:适合存储定长的数据,存储效率快
缺点:使用不当会造成存储空间的浪费
varchar
varchar可变长度的,存储的大小为输入数据的字节的实际长度,所输入的数据字符长度可以为0。取值范围0-8000
优点:适合存储不固定长度的数据,它可以识别出字节用于保存实际使用多大的长度。合理的利用的存储空间。
缺点:存储效率低
nvarchar
我们都知道英文字母占一个字节,汉字占两个字节,如果我们的数据中又有英文,又有汉字,这时nvarchar就该上场了,nvarchar无论是英文还是汉字都是用两个字节来表示。
优点:适合存储既有英文和汉字的数据
缺点:它最多能存储4000个字符,对英文存储上有些损失
网上到处都是,个人百度就可以了,特意准备这个,事倍功半,不是这些不重要,而是尽信书则不如无书,学习里面的思想,好用就行,项目中需要使用到哪个,才重点学习和掌握哪个。使用频率比较多的是工厂模式,单例模式,观察者模式,代理模式。
同理还有六大设计原则,只有6个,还是需要每个都掌握的,最终都是为了 高内聚 低耦合
因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激。