.NET 重要主题总结 - 代码示例






3.37/5 (66投票s)
.NET 访谈问题及答案,包含真实世界示例和代码片段
什么是委托?
委托封装方法,将其视为一流对象。声明委托时,后端会创建一个类。委托可以被添加/删除,称为委托链或多播委托。通过委托,您可以将方法作为参数发送,例如
public static class DeletageExample
{
private delegate void MyDelegate(string txt); //Declare Delegate
public static void DeletageExampleMethod()
{
MyDelegate del = SendText; //Create Delegate object and assign function SendText
del("Display this"); //Invoke Delegate function
}
public static void SendText(string txt)
{
Console.WriteLine(txt);
}
}
//Delegate Chaining or Multicast Delegate
public static class DeletageExample
{
private delegate void MyDelegate(string txt);
public static void DeletageExampleMethod()
{
MyDelegate txt1 = SendText;
MyDelegate txt2 = SendText2;
txt1 += txt2;
txt1("hay u");
}
public static void SendText(string txt)
{
Console.WriteLine(txt);
}
public static void SendText2(string txt)
{
Console.WriteLine(txt+" I am second delegate");
}
}
什么是匿名方法?
没有名称的方法,匿名方法用于缩短委托代码。与其创建一个方法并将其分配给委托,不如在委托赋值语句中编写方法体。只为简短方法使用匿名方法作为替代方案,例如
private delegate void MyDelegate(string txt);
public static void DeletageExampleMethod()
{
MyDelegate txt1 = delegate(string txt) { Console.WriteLine(txt); };
txt1("I am Anonymous method");
}
什么是 Lambda 表达式?
Lambda 表达式用于缩短匿名方法,Lambda 运算符 (=>) 用于替换 delegate
关键字。例如
private delegate void MyDelegate(string txt);
public static void DeletageExampleMethod()
{
MyDelegate txt1 = txt => { Console.WriteLine(txt); }; //Use () for more than
//one input parameters
txt1("I am Anonymous method");
}
什么是泛型委托?
为了使委托代码更短并消除委托声明,可以使用 Action<>
、Func<>
泛型委托。
Action<>
:无返回值,最多可接受 16 个输入参数Func<>
:有返回值,最多可接受 16 个输入参数和一个返回值
public static void DeletageExampleMethod()
{
Action<string> txt1 = txt => Console.WriteLine(txt);
txt1("I don't have output ");
Func<string, string> txt2 = txt =>
{
Console.WriteLine(txt);
return "I am returning Value";
};
}
什么是事件?
事件是一种以订阅/监听者模式工作的委托。换句话说,事件和委托携手合作。以下是一个非常基本的事件示例,关于一个人从银行提款。对于任何类型的账户,都应从余额中扣除款项。可以使用 Sender
对象和 EventArgs
来确定账户类型和请求发送者。为使示例简单化,仅实现了基本功能。
class Program
{
static void Main(string[] args)
{
var account1 = new AccountManagement();
var person = new Person(account1) { Name = "John Doe" };
account1.Winthdraw(person);
var account2 = new AccountManagement();
var person2 = new Person(account2) { Name = "Justin Phillip" };
account2.Winthdraw(person2);
Console.ReadLine();
}
}
public class Person
{
public string Name { get; set; }
private AccountManagement _account;
public Person( AccountManagement account)
{
_account = account;
account.Deposithandler += account.DeductMoney; //Subscribe to Deduct Money Event
}
}
public class AccountManagement
{
public delegate void DepostHandler(object sender, string eventArgs);
public event DepostHandler Deposithandler;
public void DeductMoney(object sender, string eventArgs)
{
Console.WriteLine(eventArgs + ": Money has been deducted from your account\n");
}
public void Winthdraw(Person person)
{
Console.WriteLine(person.Name+": You withdraw money");
Deposithandler(this, person.Name);
}
}
什么是隐式类型化或 var 关键字?
通过 var
关键字进行的隐式类型化在声明和初始化时确定变量的数据类型。Var
与某些语言中使用的 Variant 数据类型不同,仍然具有强类型特性。Var
变量不能初始化为 null
,因为编译器无法确定其数据类型(只有引用类型变量可以声明为 null
)。您需要隐式地将 null
值转换为某种引用类型,例如 string
。
var accountnum = "342425756342"; //Correct, compiled as string
accountnum = 50; //Compiler error since it is considered as string in first statement.
var username = null; //Compiler error, unable to determine the datatype
var username = (string)null; //Successfully compile, string is reference.
什么是 Entity Framework 中的 Code-First 方法?
在 Code-First 方法中,您不必担心数据库设计/开发。首先创建应用程序设计,例如模型类/实体。完成后,您可以创建继承自 DbContext
的类,并创建模型类的 DBSet
。当您访问该 DbContext
类并执行某些 CRUD 操作时,数据库将根据您在 Context 构造函数中提供的名称自动创建。在本地的 SQLExpress 中查找该数据库。
public class Student
{
public Student()
{
}
public int StudentID { get; set; }
public string StudentName { get; set; }
}
public class Standard
{
public Standard()
{
}
public int StandardId { get; set; }
public string StandardName { get; set; }
public string Description { get; set; }
}
public class Context : DbContext
{
public Context(): base("StudentDB.Test") //Database Name
{
}
public DbSet<Student> Students { get; set; }
public DbSet<Standard> Standards { get; set; }
}
class Program
{
static void Main(string[] args)
{
using (var ctx = new Context())
{
var stud = new Student() { StudentName = "New Student" };
ctx.Students.Add(stud);
ctx.SaveChanges();
}
}
}
什么是泛型?
一个简单的定义是将数据与其类型分开。在某些情况下,您可能需要在一种以上的数据类型中存储或获取数据。例如,您有一个 student
类,grade1
学生的 ID
只能是整数,而 grade2
学生的 ID
应该是 string
。这是一个 愚蠢的例子,但我是用它来概念验证的。为了有效地实现一个可以验证整数和 string
ID 的类,您可能需要声明两个 student
类。但是,如果您想为一个班级(grade1
和 grade2
)的学生使用一个类,泛型将非常有帮助。
static void Main(string[] args)
{
var Grad1Student = new Student<int> {StudentName = "John", StudentId = 1};
var Grad2Student = new Student<string> { StudentName = "John", StudentId = "R001" };
}
public class Student<T>
{
public string StudentName { get; set; }
public T StudentId { get; set; }
}
以下是一个示例,其中可以将泛型类的对象发送到方法进行进一步处理
static void Main(string[] args)
{
var Grad1Student = new Student<int> {StudentName = "John", StudentId = 1};
var Grad2Student = new Student<string> { StudentName = "John", StudentId = "R001" };
var print = new Print();
print.PrintStudents(Grad1Student);
print.PrintStudents(Grad2Student);
Console.ReadLine();
}
public class Student<T>
{
public string StudentName { get; set; }
public T StudentId { get; set; }
}
public class Print
{
public void PrintStudents<T>(Student<T> student)
{
Console.WriteLine(student.StudentId);
}
}
什么是 Reflection?
反射用于获取类、对象或程序集的元数据,例如程序集名称及其版本、程序集中所有类名的列表、类的属性/字段和方法等。您还可以获取属性/字段的值并调用方法。反射与特性(Attribute)可以帮助构建非常灵活的应用程序,您可以在运行时访问类、方法并调用它们。通过反射和特性,可以轻松导航第三方程序集。以下是一个简单的示例。
static void Main(string[] args)
{
var assembies = Assembly.GetExecutingAssembly();
Console.WriteLine("assembies are " + assembies.FullName);
var classes = assembies.GetTypes();
foreach (var classname in classes)
{
Console.WriteLine("Classes are: " + classname.FullName);
foreach (var prop in classname.GetProperties())
{
Console.WriteLine("\t"+ prop.Name);
}
foreach (var method in classname.GetMethods())
{
Console.WriteLine("\t" + method.Name);
}
}
var onlyattrubuteclass =
assembies.GetTypes().Where(t => t.GetCustomAttributes<MyClassAttribute>().Any());
foreach (var attrubteclass in onlyattrubuteclass)
{
Console.WriteLine("\n\nAttributed Class " + attrubteclass.FullName);
}
Console.ReadLine();
}
[AttributeUsageAttribute(AttributeTargets.Class)]
public class MyClassAttribute : Attribute
{
}
[MyClass]
public class MyTest
{
public string Name { get; set; }
public int Id { get; set; }
public void TestMethod()
{
}
}
什么是扩展方法?
扩展方法顾名思义,用于扩展现有类的功能。这是一个 static
方法,其第一个参数是需要扩展的类的类型,并带有额外的关键字 "this
"。扩展方法通过 (.) 运算符访问,就像 static
类方法一样,但需要类的对象。通常,扩展方法适用于我们没有第三方 DLL 代码的情况,但这方面的看法因人而异。以下是一个扩展方法的简单示例。
class Program
{
static void Main(string[] args)
{
var test = new MyTest {Name = "John"};
test.MyTestExtesnionMethod();
Console.ReadLine();
}
}
public class MyTest
{
public string Name { get; set; }
public int Id { get; set; }
}
public static class ExtendMyTest
{
public static void MyTestExtesnionMethod(this MyTest test)
{
Console.WriteLine("I am extension method " + test.Name);
}
}
什么是 Dynamic 和 Late Binding?
Dynamic
是 .NET 中一种新的类型,用于在变量初始化时绕过编译器检查,并在运行时进行检查。它更灵活但也很危险,需要更谨慎地使用。dynamic
的一个很酷的功能是能够与其他语言的代码进行交互并执行它们。例如,在示例中,PythonIron 用于与 Python 代码交互并执行它。此外,dynamic
可以与 .NET 中可用的 ExpandoObject
类一起使用,在该类中您可以声明属性并在运行时使用它们。在 ASP.NET MVC 中,ViewBag
用于在 Controller 和 Views 之间传递数据,这是一个 dynamic
的良好示例。运行 Python 代码的简单示例是通过 dynamic
完成的:(从 NuGet 获取 IronPython
)
public static void Main()
{
var pythonRuntime = Python.CreateRuntime();
dynamic pythonFile = pythonRuntime.UseFile("Test.py");
pythonFile.SayHellotoPython();
Console.ReadLine();
}
其中 Python.py 是
import sys;
def SayHellotoPython();
print "Hello I am Python"
下面给出了 ExpandoObject
的示例,它在 ASP.NET MVC 中就像 ViewBag
一样工作
public static void Main()
{
dynamic exp = new ExpandoObject();
exp.Name = "John";
exp.Age = 56;
Console.WriteLine(exp.Name);
Console.WriteLine(exp.Age);
Console.ReadLine();
}
什么是可选参数?
如果您不知道或不想在每次调用方法时都为所有参数发送值,可选参数就很有用。它是方法重载的一个很好的替代方案,您为具有相同名称的不同参数集创建单独的方法。创建可选参数时有几点需要记住:必需参数应放在开头,并且参数的顺序非常重要。例如,如果您的方法有 3 个参数,第一个是必需的,后两个是可选的,而您不想指定第二个参数的值,您将收到编译器错误,因为编译器无法映射参数。解决方法是使用命名参数,如下面的示例所示
public static void Main()
{
MyTestExample("John");
MyTestExample("John", "Doe");
MyTestExample("John", "Doe",40);
MyTestExample(firstname:"John", age:67);
Console.ReadLine();
}
public static void MyTestExample(string firstname,string lastname=null, int age=0)
{
Console.WriteLine("{0} {1} is {2} years old", firstname, lastname, age);
}
什么是 TPL(任务并行库)?
Task Parallel Library(任务并行库)可以定义为旧的 Threading 实现的替代方案,并提供了一种方便的方式来实现多个任务同时执行。任务本身可以定义为基本的执行过程。可以通过 Task Factory 轻松创建任务,并且 Task.WaitAll
或 WaitAny
函数有助于建立任务的层次结构,或者可以称为任务阻塞函数。例如,暂停执行直到给定任务的功能完成为止。任务可以是连续的,意味着如果主任务完成,您可以指定子任务。如果您想在主任务执行完成后执行任何功能(如日志记录、数据库保存等),此功能非常有用。以下是 Task
、ContinueWith
和 WaitAll
的简单示例。
static void Main(string[] args)
{
var task = Task.Factory.StartNew(() =>
MyFirtThread(1)).ContinueWith((prevtask) => ThreadCompleted("MyFirtThread"));
var task1 = Task.Factory.StartNew(() => MySecondThread(2));
var task2 = Task.Factory.StartNew(() => MyFirtThread(1));
var task3 = Task.Factory.StartNew(() => MySecondThread(2));
var tasklist = new List<Task> {task, task1, task2, task3};
Task.WaitAll(tasklist.ToArray());
Console.WriteLine("press enter to exit...");
Console.ReadLine();
}
public static void MyFirtThread(int id)
{
Thread.Sleep(1500);
Console.WriteLine("myFirtThread {0}",id);
}
public static void MySecondThread(int id)
{
Console.WriteLine("mySecondThread {0}",id);
}
public static void ThreadCompleted(string task)
{
Console.WriteLine(task + " completed");
}
任务并行库中提供的 Parallel Loop 是什么?
任务并行库中提供了 ForEach
和 For
循环,它们有助于并行运行迭代,而不是像第一个示例那样顺序执行任务(在该示例中,先执行任务,然后是任务 1 到任务 3)。Parallel.For
和 Parallel.ForEach
自动处理 WaitAll
功能,这意味着它会阻止执行直到循环的所有迭代完成执行。以下是一个简单的示例
static void Main(string[] args)
{
var intArray = new List<int> {1, 2, 3, 4, 5, 6, 7, 8, 9, 23, 5, 7, 356, 89};
Parallel.ForEach(intArray, (i) => Console.WriteLine(i));
Console.WriteLine("press enter to exit...");
Console.ReadLine();
}
什么是任务取消令牌或如何取消任务?
由于任务是并行执行的,因此提供了非常有用的取消方法。通过 TaskCancellationToken
,可以暂停所有任务的执行或处理(日志记录、跳过任务等),如果出现任何异常。以下是 CancellationToken
的简单示例。CancellationTokenSource
生成令牌以唯一标识任务,该令牌需要作为参数传递给任务执行方法,并检查其属性以确定是否请求了 Cancellation
。为了更好地进行异常处理,最好将 Cancellation
Token 传递给所有方法
static void Main(string[] args)
{
var source = new CancellationTokenSource();
var task = Task.Factory.StartNew(() => MyFirtThread(1, source.Token)).ContinueWith
((prevtask) => ThreadCompleted("MyFirtThread", source.Token));
source.Cancel();
Console.WriteLine("press enter to exit...");
Console.ReadLine();
}
public static void MyFirtThread(int id, CancellationToken token)
{
if (token.IsCancellationRequested)
{
Console.WriteLine("Thread Cancellation Requested");
token.ThrowIfCancellationRequested();
}
Thread.Sleep(1500);
Console.WriteLine("myFirtThread {0}",id);
}
public static void ThreadCompleted(string task, CancellationToken token)
{
if (token.IsCancellationRequested)
{
Console.WriteLine("Thread Cancellation Requested");
token.ThrowIfCancellationRequested();
}
Console.WriteLine(task + " completed");
}
await 和 async 是什么?
await
和 async
是与 Task Parallel Library 配合使用的结构。您仍然可以通过 Task Parallel Library (TPL) 类实现许多功能,但 await
和 async
确实节省了大量代码,并且具有内置功能,否则需要更多编码。async
关键字用于方法签名,它仅表明该方法应使用 await
关键字异步运行。没有 await
关键字,您将收到编译器的警告,并且该方法将同步运行。await
以非阻塞方式执行代码。await
和 async
的一个很好的例子是响应式 UI。例如,在桌面应用程序中,如果主线程仍在进行中,则无法执行子操作/线程。如果您单击任何按钮,您将无法执行任何其他操作,直到从调用方法返回响应。通过 async
和 await
,即使主线程正在运行,您也可以让子线程继续运行,这确实提高了用户体验。await
和 async
在许多其他场景中也很有帮助,例如调用 WebAPI
、服务或任何 Web 应用程序等。
下面给出了 Windows Form 的简单示例
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
PrintLabel("John Doe");
}
private async void PrintLabel(string name)
{
var result = await AssignValue(name);
label1.Text = result;
}
private Task<string> AssignValue(string name)
{
return Task.Factory.StartNew(() => GetValue(name));
}
private string GetValue(string name)
{
Thread.Sleep(2000);
return "Hi " + name;
}
}
历史
- 2015/3/19:创建