C#面试题基础

2023-02-12,

1.什么是GAC,他的作用是什么?

我的这篇文章有详细的介绍

https://www.cnblogs.com/zxtang/p/14313273.html

2.描述一个被protected interal 修饰的类成员的访问权限?

public 关键字是方法和成员的访问修饰符。公共访问public是允许的最高访问级别,对访问公共成员没有限制。protected 关键字是一个成员访问修饰符。受保护成员在它的类中可访问并且可由派生类访问。private 关键字是一个成员访问修饰符。私有访问是允许的最低访问级别。私有成员只有在声明它们的类和结构体中才是可访问的。internal 关键字是类型和类型成员的访问修饰符。只有在同一程序集的文件中,内部类型或成员才是可访问的。

3.const和readOnly的区别?

const表示不变常量,也就是不能被修改,readonly表示只读的,也就是不能进行写的操作。readOnly和const都用来标识常量,常量成员是在编译时定义的,不能在运行时更改。两者的区别在于:

(1).使用const关键字将const声明为字段,并且必须在声明const对其进行初始化(也就是必须声明的同时必须赋值),而readonly可以声明为只读字段时进行初始化,也可以只声明而在构造函数中进行初始化,且readonly只能初始化一次,构造后,只读字段无法更改。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace 静态变量和动态变量的区别
{
public class Teacher
{
public const int A=100;//声明正确 public const int a;//声明错误,没有赋值 public readonly int B; //Readonly可以在构造函数中初始化 public readonly int C = 300;//Readonly也可以声明同时进行初始化
public Teacher()
{
B = 200;
}
}
}

(2).const不能声明为static的,因为const是隐式静态的,而readonly可以是静态的也可以是实例的,r实例成员,所以不同的实例有不同的常量值,readonly更灵活。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace 静态变量和动态变量的区别
{
public class Student
{
private static const int A = 100;//const不能用static修饰 private readonly int B = 100;//readonly可以用static修饰 }
}

(3).const字段是编译时常量,像3.14和0这样的值是编译时常量,所以声明的时候就需要赋值,readonly是运行时常量。在程序运行的时候赋值,因此可以在构造函数中赋值或者在声明的时候赋值。

(4)const既可以被声明为字段也可以在任何函数内部声明,而readonly只能被声明为字段。

4.解释System.String和System.Text.StringBuilder的区别?

String   对象是不可改变的。每次使用   System.String   类中的方法之一时,都要在内存中创建一个新的字符串对象,也就是说对String对象进行修改需要为该新对象分配新的空间。在需要对字符串执行重复修改的情况下,与创建新的   String   对象相关的系统开销可能会非常昂贵。如果要修改字符串而不创建新的对象,则可以使用   System.Text.StringBuilder   类。例如,当在一个循环中将许多字符串连接在一起时,使用   StringBuilder   类可以提升性能。

StringBuilder类并没有String类的功能强大,只提供基本的替换和添加和删除字符串中的文本,但它的工作效率非常高,当定义StringBuilder对象时可以指定内存的内存容量,如果不指定系统就会根据对象初始化时的字符串长度来确定。它有两个主要参数Length和Capacity分别表示字符串的实际长度和字符串占据的内存空间长度。对字符串的修改就是在这个内存中进行的,大大提高了添加和替换的的效率。

StringBuilder sb=new StringBuilder("Hello,Welcome",100);//初始化对象并设置初始容量为100
sb.Append(" to www.csdn.net");
sb.Replace(old,new);//将old替换为new,作用与String.Replace()一样只是不需要在过程中复制字符。
StringBuilder的成员:
StringBuilder sb=new StringBuilder("Hello World")       定义初值为Hello World的值
StringBuilder
sb=new
StringBuilder(20);     初始化容量为20的空对象。另外StringBuilder还有MaxCapacity属性用来限定对象可以使用的最大容量。默认大约是int.MaxValue(20亿)可以在使用过程中定义sb.MaxCapacity=value;sb.Append(),给当前字符串追加字符串。
sb.AppendFormat()——添加特定格式的字符串
sb.Insert()——插入一个子字符串
sb.Remove()——从当前字符串删除字符
sb.Replace()——替换字符串中指定的字符
sb.ToString()——将sb转化为String 对象

5.什么是弱类型编程语言?var修饰的数据变量是强类型还是弱类型?

  强类型语言也称为强类型定义语言。是一种总是强制类型定义的语言,要求变量的使用要严格符合定义,所有变量都必须先定义后使用。

ava、C#、C++等都是强制类型定义的。也就是说,一旦一个变量被指定了某个数据类型,如果不经过强制转换,那么它就永远是这个数据类型了。

例如你有一个整数,如果不显式地进行转换,你不能将其视为一个字符串。

  与其相对应的是弱类型语言:数据类型可以被忽略的语言。它与强类型定义语言相反, 一个变量可以赋不同数据类型的值。

弱类型语言也称为弱类型定义语言。与强类型定义相反。像js,php等就属于弱类型语言。

  var修饰的数据变量是弱类型。

6.描述var,onject和dynamic的区别是什么?

首先三者都能够对声明的变量赋任何类型的值,var声明的变量在赋值的那一刻就决定了它是什么类型,所有的类型都派生自object. 所以它可以赋值为任何类型,dynamic不是在编译时候确定实际类型的, 而是在运行时。

var是C# 3中引入的,其实它仅仅只是一个语法. var本身并不是 一种类型, 其它两者object和dynamic是类型。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace VarObjectDynamic
{
class Program
{
static void Main(string[] args)
{
var a = 10;//var声明的变量在赋值的那一刻就决定了它是什么类型 object b = 20;//所有的类型都派生自object. 所以它可以赋值为任何类型 dynamic c = "hello";//dynamic不是在编译时候确定实际类型的, 而是在运行时。 //所以下面的代码是能够通过编译的,但是会在运行时报错
c++;
Console.WriteLine(a);
Console.WriteLine(b);
Console.WriteLine(c);
}
}
}

7.值类型和引用类型的区别是什么?

C#详解值类型和引用类型区别

在C#中值类型的变量直接存储数据,而引用类型的变量持有的是数据的引用,数据存储在数据堆中。

常见的值类型包括:byte,short,int,long,float,double,decimal,char,bool 和 struct ,值类型变量声明后,不管是否已经赋值,编译器为其分配内存。

引用类型:class(类),string,interface(接口),delegate(委托)。当声明一个类时,只在栈中分配一小片内存用于容纳一个地址(等new实例后堆上的地址将保存到该空间),而此时并没有为其分配堆上的内存空间。当

使用 new 创建一个类的实例时,分配堆上的空间,并把堆上空间的地址保存到栈上分配的小片空间中。值类型的实例通常是在线程栈上分配的(静态分配),但是在某些情形下可以存储在堆中。引用类型的对象总是在进

程堆中分配(动态分配)。

盗用一下大佬的图

值类型:

引用类型:@为存放的堆空间的地址

引用类型和值类型相同点:
 
(1)引用类型可以实现接口,值类型当中的结构体也可以实现接口;
 
(2)引用类型和值类型都继承自System.Object类。

范围方面

(1)C#的值类型包括:结构体(数值类型、bool型、用户定义的结构体),枚举,可空类型。

(2)C#的引用类型包括:数组,用户定义的类、接口、委托,object,字符串。

内存分配方面:

(1)数组的元素不管是引用类型还是值类型,都存储在托管堆上。

(2)引用类型在栈中存储一个引用,其实际的存储位置位于托管堆。简称引用类型部署在托管推上。而值类型总是分配在它声明的地方:作为字段时,跟随其所属的变量(实例)存储;作为局部变量时,存储在栈上。

(栈的内存是自动释放的,堆内存是在NET中会由GC来自动释放)

适用场合

(1)值类型在内存管理方面具有更好的效率,并且不支持多态,适合用做存储数据的载体;引用类型支持多态,适合用于定义应用程序的行为。

引用类型可以派生出新的类型,而值类型不能,因为所有的值类型都是密封(seal)的;
引用类型可以包含null值,值类型不能(可空类型功能允许将 null 赋给值类型,如   int? a = null;  );
引用类型变量的赋值只复制对对象的引用,而不复制对象本身。而将一个值类型变量赋给另一个值类型变量时,将复制包含的值。

值得注意的是,引用类型和值类型都继承自System.Object类。不同的是,几乎所有的引用类型都直接从System.Object继承,而值类型则继承其子类,即直接继承System.ValueType。即System.ValueType本身是一个类类

型,而不是值类型。其关键在于ValueType重写了Equals()方法,从而对值类型按照实例的值来比较,而不是引用地址来比较。

8.Ref和out修饰的参数分别代表什么意思?

ref 关键字使参数按引用传递。其效果是,在方法中对参数的任何更改都将反映在该变量中。若要使用 ref 参数,则方法定义和调用方法都必须显式使用 ref 关键字。

out 关键字会导致参数通过引用来传递。这与 ref 关键字类似,不同之处在于 ref 要求变量必须在传递之前进行初始化,而out是要把参数清空,就是说你无法把一个数值从out传递进去,out进去后,参数的数值为空,所以离

开当前方法前必须对out修饰的变量赋一次值。若要使用 out 参数,方法定义和调用方法都必须显式使用out 关键字。

如上所示,使用ref传递参数,参数必须要初始化。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace ref和out
{
class Program
{
static void Main(string[] args)
{
int b=1;
Method1(ref b);
Console.WriteLine("b="+b);//输出100
int c;
Method2(out c);
Console.WriteLine("c="+c);//输出200
Console.ReadLine();
} static void Method1(ref int a)
{
a = 100;
} static void Method2(out int a)
{
a = 300;//必须在离开方法前对a赋值,否则回报错
}
}
}

9.什么是后期绑定或者晚期绑定,如何实现后期绑定?

后期绑定又可以称为动态绑定(dynamic binding),需要使用System.Reflection,动态地获取程序集里封装的方法、属性等。这种后期绑定的方法大量的被使用在IDE和UI设计中,后期绑定的优点是可

以保存对任何对象的引用。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks; namespace Binding
{
class Program
{
static void Main(string[] args)
{
Type _type = Type.GetType("Binding.Employee");
Console.WriteLine(_type.FullName);
Console.WriteLine(_type.Namespace);
Console.WriteLine(_type.Name); Console.WriteLine("****************************程序集中的所有信息集合***************************");
Console.WriteLine("****************************程序集中的所有属性信息***************************");
PropertyInfo[] info = _type.GetProperties();//获取属性信息
foreach (PropertyInfo propinfo in info)
{
Console.WriteLine(propinfo.Name);
}
Console.WriteLine("****************************程序集中的所有方法信息***************************");
MethodInfo[] methods = _type.GetMethods();//获取方法
foreach (MethodInfo methodinfo in methods)
{
Console.WriteLine(methodinfo.Name+""+"\t"+methodinfo.ReturnType.Name);//获取此方法名和方法返回类型
} Console.Read();
}
}
public class Employee
{
public int ID { get; set; }
public string Name { get; set; }
public float Salary { get; set; } public Employee()
{
ID = -1;
Name = string.Empty;
Salary = 0;
}
public Employee(int id, string name, float salary)
{
ID = id;
Name = name;
Salary = salary;
} }
}

输出结果为:

Binding.Employee
Binding
Employee
****************************程序集中的所有信息集合***************************
****************************程序集中的所有属性信息***************************
ID
Name
Salary
****************************程序集中的所有方法信息***************************
get_ID Int32
set_ID Void
get_Name String
set_Name Void
get_Salary Single
set_Salary Void
Equals Boolean
GetHashCode Int32
GetType Type
ToString String

每个属性都有set和get方法

10.描述一下new关键字的三种用法?

在 C# 中,new 关键字可用作运算符、修饰符或约束。
1)new 运算符:用于创建对象和调用构造函数。
2)new 修饰符:在用作修饰符时,new 关键字可以显式隐藏从基类继承的成员。
3)new 约束:用于在泛型声明中约束可能用作类型参数的参数的类型。

第一种用法就不介绍了,下面介绍第二种和第三种用法

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace new关键字三种用法
{
//用法2
public class Father
{
public string Name { get; set; }
public int Age { get; set; }
public int Height { get; set; }
public string Job { get; set; }
public void Talk()
{
Console.WriteLine("Father is talking");
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace new关键字三种用法
{
public class Son:Father
{
new public string Job { get; set; }//隐藏和基类中的同名属性
new public void Talk() //隐藏和基类中的同名方法
{
Console.WriteLine("Son is talking");
}
}
}

用法3

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace new关键字三种用法
{
public class Teacher
{
//如果有了有参构造函数,那么无参构造函数要显示的写出来,如果没有有参构造函数,系统会默认有无参构造函数,无参构造函数可写可不写
public Teacher()
{ }
public Teacher(string name)
{ }
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace new关键字三种用法
{
class Program
{
static void Main(string[] args)
{
MyClass<Teacher> myClass = new MyClass<Teacher>();
myClass.test1();
Console.ReadLine();
} }
//使用new关键字修饰的T必须要有无参构造函数
class MyClass<T> where T : new()
{
public void test1()
{
Console.WriteLine("哈哈");
}
}
}

11.描述一下接口和基类的区别,定义接口的目的是什么?

使用接口和基类都可以实现多态:

(1)通过继承实现多态,基类类型作为方法的返回值类型,实际上返回的是一个具体的子类对象;基类类型作为方法参数类型实际上传递的是具体的子类对象。

(2)接口实现多态,接口作为方法返回值类型,实际上返回的是一个接口实现类对象;接口类型作为方法参数类型实际上传递的是一个接口实现类对象。

当开发一个软件的时候,可能很多时候设计完一个对象之后,并不需要马上考虑或者不知道这个对象具体怎样编写,只知道他要干什么,但不知道怎么实现,还有一种情况就是团队开发,比如一个项目

设计分为10个模块,10个小组完成,怎么做到各自开发各的模块呢?这时候使用接口就可以解决。接口只有方法规定,但是没有方法的具体实现。

通常实现多态要么通过抽象类。要么通过接口,都遵循里氏替换原则,即父类出现的地方可以使用子类替换。抽象类和接口的区别:

(1)接口侧重的是功能的封装,抽象类侧重的是代码的复用和继承,通常把公共行为放到基类中,然后需要多态的行为放到接口中。

(2)接口更简洁,使用更方便,框架设计中,使用的大都是接口而不是抽象类。

下面见一个例子,具体说明了通过继承实现多态和接口实现多态:

例子1:通过继承实现多态

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace InherianceAndPolymoriphism2
{
abstract class Person
{
//公共属性
public string Name { get; set; }
public string Id { get; set; }
public DateTime DateofBirth { get; set; }
//protected只能供子类使用,也就是只能在子类内部使用,不能通过子类对象去使用
protected string PhoenNumber { get; set; }
//构造方法
public Person() { }
public Person(string Name,string Id) {
this.Name = Name;
this.Id = Id;
}
//共同的行为:可以放在父类中
public void Dowork1()
{
Console.WriteLine($"【调用父类的公共方法】:{Name}正在工作");
} public void Dowork2() {
Console.WriteLine($"【调用父类的公共方法】:{Name}正在写作");
} //父类私有的成员
private void PersonDoWork()
{
Console.WriteLine($"{Name}正在泡妞");
} //抽象方法:父类规范一个行为要求(没有方法体),具体实现由子类来实现,具有强制性,即子类必须要实现该行为
//抽象方法只能包含在抽象类中,但是抽象类可以不包含抽象方法,且抽象类不能通过new构造
public abstract void Have(); //虚方法:父类可以对该方法有实现,子类也可以对该方法进行重写,不具有强制性,即子类也可以对该方法不进行重写
public virtual void Sleep()
{
Console.WriteLine($"【父类实现的虚方法】:{Name}正在睡觉");
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace 继承和多态
{
public class Student:Person
{
//属性
public int StudentId { get; set; }
public string ClassName { get; set; }
//构造方法
public Student() { }
public Student(string name, string id, string ClassName, int StudentId) : base(name, id)
{
//base.Name = name;
//base.Id = id; //或者这样继承,不过上述继承参数的方式写比较简便
this.ClassName = ClassName;
this.StudentId = StudentId;
}
//实现抽象类方法:关键字为override,必须实现
public override void Have()
{
Console.WriteLine($"【Student子类实现父类中的抽象方法】:{Name}正在吃饭");
} //重写虚方法,关键字为override
//父类中的虚方法不调用,除非子类主动调用base.Sleep(),父类中的虚方法既可以在父类中实现,也可以在子类中进行重写,或者两者都不实现
public override void Sleep()
{
Console.WriteLine($"【Student子类重写父类中的虚方法】:{Name}正在睡觉");
base.Sleep();//子类调用父类的虚方法
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace 继承和多态
{
class Program
{
static void Main(string[] args)
{
Student student = new Student();
Person p = new Student();//里氏替换原则
//父类作为方法返回值类型,实际返回的是一个具体的子类对象;/父类作为方法参数类型,实际传递的是一个具体的子类对象
p = TestStudent(student);
Console.ReadKey();
}
private static Person TestStudent(Person p)
{
p.Name = "小唐";
//调用父类的公共方法,无法调用私有方法
p.Dowork1();
p.Dowork2();
//调用抽象方法
p.Have();
p.Sleep();
return p;
} }
}

输出结果:

【调用父类的公共方法】:小唐正在工作
【调用父类的公共方法】:小唐正在写作
【Student子类实现父类中的抽象方法】:小唐正在吃饭
【Student子类重写父类中的虚方法】:小唐正在睡觉
【父类实现的虚方法】:小唐正在睡觉

例子2:通过接口实现多态

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace 继承和多态
{
abstract public class Person
{
//公共属性
public string Name { get; set; }
public string Id { get; set; } //构造方法
public Person() { }
public Person(string Name, string Id)
{
this.Name = Name;
this.Id = Id;
} }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace 继承和多态
{
//定义演讲接口
public interface IMeeting
{
//规定行为:行为不能有方法体,且不能加public /// <summary>
/// 演讲
/// </summary>
void Speech(); /// <summary>
/// 谈话
/// </summary>
void Talk();
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace 继承和多态
{
//定义教学接口
public interface ITeach
{
/// <summary>
/// 授课
/// </summary>
void Lecture(); /// <summary>
/// 教学研究
/// </summary>
void StudyCourse();
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace 继承和多态
{
public class Teacher : Person, ITeach
{
public override void Have()
{
Console.WriteLine($"{Name}正在吃饭");
} public void Lecture()
{
Console.WriteLine($"{Name}正在授课");
} public void StudyCourse()
{
Console.WriteLine($"{Name}正在教学研究");
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace 继承和多态
{
public class President : Person,IMeeting
{
//实现抽象类方法
public override void Have()
{
Console.WriteLine($"{Name}正在吃饭");
} //实现接口
public void Speech()
{
Console.WriteLine($"{Name}正在演讲");
}
//实现接口
public void Talk()
{
Console.WriteLine($"{Name}正在谈话");
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace 继承和多态
{
class Program
{
static void Main(string[] args)
{
//里氏替换原则
IMeeting meeting1 = new President() { Name = "唐主任" };
ITeach teach1 = new Teacher() { Name = "唐老师" };
TestIMeeting(meeting1);
TestITeach(teach1);
meeting1.Speech();
teach1.Lecture();
Console.ReadKey();
} //接口实现多态方式一:接口作为方法参数类型,实际传递的是接口的实现类对象President
private static IMeeting TestIMeeting(IMeeting meeting)
{
//接口实现多态方式二:接口作为方法返回值类型,实际返回的是接口的实现类对象President
return new President();
} //接口实现多态方式一:接口作为方法参数类型,实际传递的是接口的实现类对象Teacher
private static ITeach TestITeach(ITeach teach)
{
//接口实现多态方式二:接口作为方法返回值类型,实际返回的是接口的实现类对象Teacher
return new Teacher();
}
}
}

输出结果:

唐主任正在演讲
唐老师正在授课

12.什么是委托,委托和事件的关系是什么?

委托是一种程序特性,委托可代表一个或者多个同类型的方法,简单的说委托就是方法的变量,能够代表方法。委托的使用步骤如下:

【1】声明委托,定义一个方法的“样子”,也就是我们说的原型,就是指方法的返回值类型、方法的参数类型和个数。

【2】根据委托编写具体的方法

【3】创建委托变量

【4】委托变量关联具体方法(多路委托,使用+=关联多个方法,调用委托的时候将会按顺序执行各个方法)

【5】调用委托

委托的应用:当你在对象之间传递数据的时候,普通方法根本搞不定,这时候使用委托一定能够搞定,主要用在跨线程访问中,例如:

正常情况下:在A对象中创建了B对象,这时候如果B对象中有一个公共行为C,那么在A中是可以调用的

class A

{

B b=new B();

void MyMethod()

{

b.C();

}

void  NewMethod()

{

}

}

问题是如果想在B中调用A中的行为NewMethod是否可以?当然不行!但是通过委托是可以的。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace 委托
{
public class A
{
B b = new B();
public void MyMethod()
{
b.C();
//【4】将委托变量关联具体方法
b.newMethodDelegate += NewMethod;
//通过委托实现b调用A中的方法NewMethod
b.B_NewMethod();
} void NewMethod()
{
Console.WriteLine("执行A中的NewMethod方法");
}
} //【1】声明委托
public delegate void NewMethodDelegate();
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace 委托
{
public class B
{
//【3】创建委托变量
public NewMethodDelegate newMethodDelegate ;
public void C()
{
Console.WriteLine("执行B中的C方法");
}
//【5】调用委托
public void B_NewMethod()
{
newMethodDelegate();
}
}
}

输出结果:

执行B中的C方法
执行A中的NewMethod方法

事件概念:事件其实是对象对外界信息的刺激,产生的一种消息响应机制。本质上事件其实是委托的进一步包装。

随便选取一个按钮事件做例子:

【1】声明委托

public delegate void EventHandler(object sender, EventArgs e);

【2】定义一个委托事件

public event EventHandler Click;

【3】关联委托

this.btn_Login.Click += new System.EventHandler(this.btn_Login_Click);//将事件和方法关联

事件和委托对比不同点:

第一、事件无法直接赋值,比如事件=null;会出现编译错误,而委托可以。

好处:避免用户对事件直接操作,比如Click事件,如果允许Click=null,会把底层代码清除!可以起到保护。

委托相对太“开放”。

第二、event对象没有invoke()方法,只能通过使用括号的方式来运行。

委托和事件的选择:

第一、正常解决问题,你使用委托和事件没有什么本质区别。所以,我们建议是使用委托。

第二、如果我们做控件二次开发,扩展控件的事件的时候,那必须用事件。

13.描述一下创建并启动一个线程的基本过程?

14什么是序列化,序列化常用的三种格式是什么?

如果我们给自己写的类标识[Serializable]特性,我们就能将这些类序列化。除非类的成员标记了[NonSerializable],序列化会将类中的所有成员都序列化。

(1)BinaryFormatter序列化:将对象序列化二进制流格式。

(2)SoapFormatter序列化:SOAP是一种轻量的、简单的、基于XML的协议,它被设计成在WEB上交换结构化的和固化的信息。 SOAP 可以和现存的许多因特网协议和格式结合使用,包括超文本传   输协议(HTTP),简单邮件传输协议(SMTP),多用途网际邮件扩充协议(MIME)。SOAP序列化的主要优势在于可移植性。SoapFormatter把对象序列化成SOAP消息或解析SOAP消息并重构被序 列化的对象。

(3)XML序列化方式:将对象序列化为XML文件。

15.Trace和Debug类的作用是什么,二者有什么区别?

在 C# 语言中允许使用Trace和Debug类输出程序运行时的调试信息,类似于使用 Console.WriteLine 的方式向控制台输出信息。
所谓调试信息是程序员在程序运行时需要获取的程序运行的过程,以便程序员更好地解决程序中出现的问题,这种调试也被称为是非中断调试。

需要注意的是当程序在 Debug 状态下执行时使用 Debug 类和Trace类均可以在输出窗口中显示输出信息,而在 Release 状态下执行时只有 Trace 类输出的内容才会显示在输出窗口中。

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace Trace和Debug
{
class Program
{
static void Main(string[] args)
{
Trace.WriteLine("Trace");
Debug.WriteLine("Debug");
Console.ReadKey();
}
}
}

Debug调试状态下的输出:

Release状态下的调试输出:

16WCF框架解决的是什么问题?

17.WCF中的ABC分别指的是什么?

18.描述WCF中三种服务实例上下文模式?

19.WPF绑定数据的4种模式?

20.什么是依赖属性,其作用是什么?

21.什么是逻辑树?可视树?他们和默认模板的关系是什么?

C#面试题基础的相关教程结束。

《C#面试题基础.doc》

下载本文的Word格式文档,以方便收藏与打印。