Bộ câu hỏi phỏng vấn C#/.Net phần 6

Tại sao sử dụng câu lệnh lock trong C#?


Từ khóa lock đảm bảo rằng một thread không tiến vào một khu vực mã quan trọng khi đang có một thread khác nằm trong đó. Nếu một thread khác cố gắng tiến vào mã bị lock, nó sẽ đợi, chặn, cho đến khi đối tượng được giải phóng.

Từ khóa lock gọi Enter ở đầu khối và Exit ở cuối khối. Từ khóa lock thực sự xử lý lớp Monitor ở phía sau.

private static readonly Object obj = new Object();

lock (obj)
{
   // khu vực mã quan trọng
}

Cấu trúc dữ liệu của bạn nên triển khai interface nào để làm cho phương thức Where hoạt động?


Việc triển khai IEnumerable interface giúp có khả năng sử dụng foreachwhere.

Các tác vụ bất đồng bộ Async/Await hoạt động như thế nào trong .NET?


Vấn đề:

private async Task<bool> TestFunction()
{
   var x = await DoesSomethingExists();
   var y = await DoesSomethingElseExists();
   return y;
}

Câu lệnh await thứ hai có được thực thi ngay lập tức hay là sau khi câu lệnh await đầu tiên trả về?

Giải pháp:

  • await tạm dừng phương thức cho đến khi hoạt động hoàn tất. Vì vậy, await thứ hai sẽ được thực thi sau khi await đầu tiên trả về.
  • Mục đích của await là nó sẽ trả lại thread hiện tại cho thread pool (nhóm thread) khi hoạt động được await kết thúc và thực hiện bất cứ điều gì.
  • Điều này đặc biệt hữu ích trong các môi trường hiệu suất cao, chẳng hạn như một máy chủ web, nơi một request được xử lý trên một thread nhất định từ thread pool tổng thể. Nếu chúng ta không sử dụng await, thì thread đã cho xử lý request (và tất cả tài nguyên của nó) vẫn "được sử dụng" khi lệnh gọi database / service hoàn tất. Quá trình này có thể mất vài giây hoặc hơn, đặc biệt là đối với các cuộc gọi service bên ngoài.

Indexer trong C# là gì?


Indexer là một tiện ích cú pháp cho phép bạn tạo một class, struct hoặc interface mà các ứng dụng client có thể truy cập dưới dạng một array.

Để khai báo một indexer trên một class hoặc struct, hãy sử dụng từ khóa this:

// Indexer declaration
public int this[int index] {
   // get and set accessors
}

Sau đó, bạn có thể truy cập instance của class này bằng cách sử dụng toán tử truy cập mảng []. Các indexer có thể được overload (nạp chồng). Các indexer có thể được khai báo với nhiều tham số và mỗi tham số có thể là một kiểu khác nhau. Không nhất thiết các index phải là số nguyên. C# cho phép các index thuộc các kiểu khác, ví dụ, một string.

class IndexedNames {
   private string[] namelist = new string[size];

   public string this[int index] {
      get {
            string tmp;
            if (index >= 0 && index <= size - 1) {
                 tmp = namelist[index];
            } else {
                 tmp = "";
            }

            return (tmp);
      }

      set {
            if (index >= 0 && index <= size - 1) {
                 namelist[index] = value;
            }
      }
   }
}

Khi nào sử dụng ArrayList thay vì array[] trong C#?


  • Array là strongly typed và hoạt động tốt như các tham số. Nếu bạn biết độ dài của collection của mình và nó cố định, bạn nên sử dụng một array.
  • ArrayList không phải là strongly typed, mọi Insertion hoặc Retrial (truy xuất) sẽ cần một chuyển đổi (cast) để quay trở lại kiểu ban đầu của bạn. Nếu bạn cần một phương thức để lấy một danh sách của một kiểu cụ thể, thì ArrayList sẽ ngắn hơn vì bạn có thể truyền vào ArrayList bất kỳ kiểu nào. ArrayList sử dụng một mảng tự động mở rộng bên trong, nghĩa là nó sẽ tự động thay đổi kích thước khi bạn thêm/xóa phần tử trong ArrayList.
  • Những gì bạn thực sự muốn sử dụng là một generic list như List.
  • List có tất cả các lợi thế của Array và ArrayList. Nó là strongly typed và nó hỗ trợ độ dài biến đổi của các item.

Một phương thức có thể được nạp chồng theo những cách nào?


Các phương thức có thể được nạp chồng (overload) bằng cách sử dụng các kiểu dữ liệu khác nhau cho tham số, thứ tự khác nhau của tham số và số lượng tham số khác nhau.

Các kiểu con trỏ trong C# là gì?


Các biến kiểu con trỏ (pointer type) lưu trữ địa chỉ bộ nhớ của một kiểu khác. Con trỏ trong C# có cùng khả năng với con trỏ trong C hoặc C++.

Phạm vi của biến thành viên Protected Internal trong một lớp C# là gì?


Protected internal access specifier cho phép một lớp ẩn các biến thành viên và các hàm thành viên của nó khỏi các đối tượng và hàm của lớp khác, ngoại trừ một lớp con trong cùng một ứng dụng. Điều này cũng được sử dụng khi thực hiện kế thừa.

class access
{
    // String Variable declared as protected internal
    protected internal string name;
    public void print()
    {
        Console.WriteLine("\nMy name is " + name);
    }
}

Công dụng của chỉ thị tiền xử lý có điều kiện (conditional preprocessor directive) trong C# là gì?


Bạn có thể sử dụng chỉ thị #if để tạo một chỉ thị có điều kiện. Các chỉ thị có điều kiện rất hữu ích để kiểm tra một ký hiệu hoặc các ký hiệu để kiểm tra xem chúng có là true hay không. Nếu chúng là true, trình biên dịch sẽ thực hiện tất cả mã giữa #if và chỉ thị tiếp theo.

#if DEBUG
    Console.WriteLine("Debug version");
#endif

Sự khác biệt giữa lớp System.ApplicationException và lớp System.SystemException là gì?


  • Lớp System.ApplicationException hỗ trợ các exception do các chương trình ứng dụng tạo ra. Do đó, các exception được định nghĩa bởi các lập trình viên nên kế thừa từ lớp này.
  • Lớp System.SystemException là lớp cơ sở cho tất cả các exception hệ thống được xác định trước.

Sự khác biệt giữa StackOverflowError và OutOfMemoryError là gì?


  • OutOfMemoryError liên quan đến Heap. Cần tránh: Đảm bảo có sẵn các đối tượng không cần thiết cho Garbage Collector.
  • StackOverflowError liên quan đến stack. Cần tránh: Đảm bảo rằng các cuộc gọi phương thức có kết thúc (không phải trong một vòng lặp vô hạn).

Khi nào bạn sẽ sử dụng các delegate trong C#?


Bây giờ chúng ta có các biểu thức lambda và các phương thức anonymous (ẩn danh) trong C#, chúng ta sử dụng các delegate nhiều hơn nữa. Trong C# 1, ở đó bạn luôn phải có một phương thức riêng để triển khai logic, việc sử dụng một delegate thường không có ý nghĩa. Ngày nay, chúng ta sử dụng delegate cho:

  • Event handlers (dành cho GUI và các thứ khác).
  • Start các thread.
  • Callbacks (ví dụ: đối với API bất đồng bộ).
  • LINQ và tương tự (List.Find, v.v.).
  • Bất kỳ nơi nào khác mà chúng ta muốn áp dụng mã "mẫu" một cách hiệu quả với một số logic chuyên dụng bên trong (nơi delegate cung cấp chuyên môn).

Có thể thực hiện đa kế thừa trong C# không?


Trong C#, các lớp kế thừa chỉ có thể kế thừa từ một lớp cơ sở. Nếu bạn muốn kế thừa từ nhiều lớp cơ sở, hãy sử dụng interface.

Từ khóa yield được sử dụng để làm gì trong C#?


Vấn đề:

IEnumerable<object> FilteredList()
{
   foreach( object item in FullList )
   {
      if( IsItemInPartialList( item )
         yield return item;
   }
}

Bạn có thể giải thích từ khóa yield làm gì ở trong đoạn mã trên không?

Giải pháp: Hàm trên trả về một đối tượng thực thi IEnumerable interface. Nếu một hàm đang gọi bắt đầu foreach-in qua đối tượng này thì hàm được gọi lại cho đến khi nó yield. Đây là cú pháp được giới thiệu trong C# 2.0.

yield có hai cách sử dụng tuyệt vời:

  • Nó giúp cung cấp sự lặp tùy chỉnh mà không cần tạo các collection tạm.
  • Nó giúp thực hiện lặp trạng thái.
  • Lợi thế của việc sử dụng yield là nếu hàm sử dụng dữ liệu của bạn chỉ cần item đầu tiên của collection, thì item còn lại sẽ không được tạo.

Một ví dụ khác:

public void Consumer()
{
   foreach(int i in Integers())
   {
       Console.WriteLine(i.ToString());
   }
}

public IEnumerable<int> Integers()
{
   yield return 1;
   yield return 2;
   yield return 4;
   yield return 8;
   yield return 16;
   yield return 16777216;
}

Khi bạn xem qua ví dụ này, bạn sẽ thấy lệnh gọi đầu tiên đến Integers() trả về 1. Lệnh gọi thứ hai trả về 2 và dòng yield return 1 không được thực thi lại.

Avatar Techmely Team
VIẾT BỞI

Techmely Team