Truyền so với sử dụng từ khóa 'as' trong CLR


387

Khi lập trình giao diện, tôi thấy rằng tôi đang thực hiện rất nhiều chuyển đổi kiểu hoặc đối tượng.

Có sự khác biệt giữa hai phương pháp chuyển đổi này không? Nếu vậy, có sự khác biệt về chi phí hay điều này ảnh hưởng đến chương trình của tôi như thế nào?

public interface IMyInterface
{
    void AMethod();
}

public class MyClass : IMyInterface
{
    public void AMethod()
    {
       //Do work
    }

    // Other helper methods....
}

public class Implementation
{
    IMyInterface _MyObj;
    MyClass _myCls1;
    MyClass _myCls2;

    public Implementation()
    {
        _MyObj = new MyClass();

        // What is the difference here:
        _myCls1 = (MyClass)_MyObj;
        _myCls2 = (_MyObj as MyClass);
    }
}

Ngoài ra, "nói chung" phương pháp ưa thích là gì?


Bạn có thể thêm một ví dụ nhỏ về lý do tại sao bạn sử dụng các diễn viên ở vị trí đầu tiên cho câu hỏi hoặc có thể bắt đầu một câu hỏi mới không? Tôi quan tâm đến lý do tại sao bạn chỉ cần diễn viên để thử nghiệm đơn vị. Tôi nghĩ rằng nó nằm ngoài phạm vi của câu hỏi này mặc dù.
Erik van Brakel

2
Tôi có thể có thể thay đổi bài kiểm tra đơn vị của mình để ngăn chặn nhu cầu này. Về cơ bản, điều này cho thấy rằng tôi có một tài sản trên đối tượng cụ thể không có trong giao diện. Tôi cần đặt tài sản đó nhưng thực tế tài sản đó sẽ được đặt bằng các phương tiện khác. Câu trả lời đó có đáp ứng được câu hỏi của bạn không?
Frank V

Như Patrik Hägne astutely chỉ ra dưới đây, có một sự khác biệt.
Neil

Câu trả lời:


519

Câu trả lời dưới đây được viết vào năm 2008.

Kết hợp mẫu giới thiệu C # 7, phần lớn đã thay thế astoán tử, như bây giờ bạn có thể viết:

if (randomObject is TargetType tt)
{
    // Use tt here
}

Lưu ý rằng ttvẫn còn trong phạm vi sau này, nhưng không được chỉ định chắc chắn. (Đó chắc chắn giao trong ifcơ thể.) Đó là một chút khó chịu trong một số trường hợp, vì vậy nếu bạn thực sự quan tâm về việc giới thiệu số lượng nhỏ nhất của các biến thể trong tất cả các phạm vi, bạn vẫn có thể muốn sử dụng istiếp theo là một diễn viên.


Tôi không nghĩ bất kỳ câu trả lời nào cho đến nay (tại thời điểm bắt đầu câu trả lời này!) Đã thực sự giải thích nơi nào đáng để sử dụng.

  • Đừng làm điều này:

    // Bad code - checks type twice for no reason
    if (randomObject is TargetType)
    {
        TargetType foo = (TargetType) randomObject;
        // Do something with foo
    }

    Điều này không chỉ kiểm tra hai lần, mà còn có thể kiểm tra những thứ khác nhau, nếu randomObjectlà một trường chứ không phải là một biến cục bộ. Có thể cho "nếu" vượt qua nhưng sau đó diễn viên thất bại, nếu một luồng khác thay đổi giá trị randomObjectgiữa hai.

  • Nếu randomObjectthực sự nên là một ví dụ TargetType, nghĩa là nếu không, điều đó có nghĩa là có lỗi, thì việc truyền là một giải pháp phù hợp. Điều đó ném ra một ngoại lệ ngay lập tức, điều đó có nghĩa là không có thêm công việc nào được thực hiện theo các giả định không chính xác và ngoại lệ hiển thị chính xác loại lỗi.

    // This will throw an exception if randomObject is non-null and
    // refers to an object of an incompatible type. The cast is
    // the best code if that's the behaviour you want.
    TargetType convertedRandomObject = (TargetType) randomObject;
  • Nếu randomObject có thể là một thể hiện của TargetTypeTargetTypelà một loại tham chiếu, thì hãy sử dụng mã như thế này:

    TargetType convertedRandomObject = randomObject as TargetType;
    if (convertedRandomObject != null)
    {
        // Do stuff with convertedRandomObject
    }
  • Nếu randomObject có thể là một thể hiện của TargetTypeTargetTypelà một loại giá trị, thì chúng ta không thể sử dụng asvới TargetTypechính nó, nhưng chúng ta có thể sử dụng một loại nullable:

    TargetType? convertedRandomObject = randomObject as TargetType?;
    if (convertedRandomObject != null)
    {
        // Do stuff with convertedRandomObject.Value
    }

    (Lưu ý: hiện tại điều này thực sự chậm hơn so với + diễn viên . Tôi nghĩ rằng nó thanh lịch và nhất quán hơn, nhưng chúng ta sẽ đi.)

  • Nếu bạn thực sự không cần giá trị được chuyển đổi, nhưng bạn chỉ cần biết liệu đó có phải phiên bản của TargetType hay không, thì istoán tử là bạn của bạn. Trong trường hợp này, không quan trọng việc TargetType là loại tham chiếu hay loại giá trị.

  • Có thể có các trường hợp khác liên quan đến thuốc generic isrất hữu ích (vì bạn có thể không biết liệu T có phải là loại tham chiếu hay không, vì vậy bạn không thể sử dụng như) nhưng chúng tương đối mù mờ.

  • Tôi gần như chắc chắn đã sử dụng ischo trường hợp loại giá trị trước đây, không nghĩ đến việc sử dụng loại nullable và ascùng nhau :)


EDIT: Lưu ý rằng không có điều nào ở trên nói về hiệu suất, ngoại trừ trường hợp loại giá trị, trong đó tôi đã lưu ý rằng việc hủy hộp đến loại giá trị null thực sự chậm hơn - nhưng nhất quán.

Theo câu trả lời của naasking, is-and-cast hoặc is-and-as đều nhanh như kiểm tra bằng và không null với các JIT hiện đại, như được hiển thị theo mã dưới đây:

using System;
using System.Diagnostics;
using System.Linq;

class Test
{
    const int Size = 30000000;

    static void Main()
    {
        object[] values = new object[Size];
        for (int i = 0; i < Size - 2; i += 3)
        {
            values[i] = null;
            values[i + 1] = "x";
            values[i + 2] = new object();
        }
        FindLengthWithIsAndCast(values);
        FindLengthWithIsAndAs(values);
        FindLengthWithAsAndNullCheck(values);
    }

    static void FindLengthWithIsAndCast(object[] values)        
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            if (o is string)
            {
                string a = (string) o;
                len += a.Length;
            }
        }
        sw.Stop();
        Console.WriteLine("Is and Cast: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }

    static void FindLengthWithIsAndAs(object[] values)        
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            if (o is string)
            {
                string a = o as string;
                len += a.Length;
            }
        }
        sw.Stop();
        Console.WriteLine("Is and As: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }

    static void FindLengthWithAsAndNullCheck(object[] values)        
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            string a = o as string;
            if (a != null)
            {
                len += a.Length;
            }
        }
        sw.Stop();
        Console.WriteLine("As and null check: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }
}

Trên máy tính xách tay của tôi, tất cả thực hiện trong khoảng 60ms. Hai điều cần lưu ý:

  • Không có sự khác biệt đáng kể giữa chúng. (Trong thực tế, có những tình huống trong đó như-cộng-null-kiểm tra chắc chắn chậm Đoạn mã trên thực sự làm cho các loại kiểm tra dễ dàng bởi vì nó cho một lớp niêm phong;. Nếu bạn kiểm tra đang cho một giao diện, những lời khuyên cân bằng hơi ủng hộ as-plus-null-check.)
  • Tất cả đều cực kỳ nhanh. Điều này chỉ đơn giản sẽ không là nút cổ chai trong mã của bạn trừ khi bạn thực sự sẽ không làm với các giá trị sau đó.

Vì vậy, đừng lo lắng về hiệu suất. Hãy lo lắng về tính đúng đắn và nhất quán.

Tôi duy trì rằng cả-và-cast (hoặc is-and-as) đều không an toàn khi xử lý các biến, vì loại giá trị mà nó đề cập có thể thay đổi do một luồng khác giữa phép thử và phép đúc. Đó sẽ là một tình huống khá hiếm gặp - nhưng tôi muốn có một quy ước mà tôi có thể sử dụng một cách nhất quán.

Tôi cũng duy trì rằng kiểm tra as-then-null giúp phân tách mối quan tâm tốt hơn. Chúng tôi có một tuyên bố cố gắng chuyển đổi và sau đó một tuyên bố sử dụng kết quả. Is-and-cast hoặc is-and-as thực hiện một thử nghiệm và sau đó một nỗ lực khác để chuyển đổi giá trị.

Nói một cách khác, sẽ có ai từng viết:

int value;
if (int.TryParse(text, out value))
{
    value = int.Parse(text);
    // Use value
}

Đó là loại những gì đang diễn ra - mặc dù rõ ràng là theo một cách khá rẻ hơn.


7
Dưới đây là chi phí của / như / đúc theo IL: atalasoft.com/cs/bloss/stevehawley/archive/2009/01/30/ trên
plinth

3
Trong trường hợp, nếu targetObject có thể là loại mục tiêu, tại sao sử dụng "is" và kết hợp cast được coi là một thực tiễn xấu? Ý tôi là, nó tạo ra một mã chậm hơn, nhưng trong trường hợp này, ý định rõ ràng hơn so với AS, như "Làm gì đó nếu targetObject là targetType", thay vì "Làm gì đó nếu targetObject là null", thì mệnh đề AS sẽ tạo ra một biến không cần thiết ngoài phạm vi IF.
Valera Kolupaev

2
@Valera: Điểm tốt, mặc dù tôi đề nghị rằng thử nghiệm as / null đủ thành ngữ để ý định rõ ràng với hầu hết tất cả các nhà phát triển C #. Cá nhân tôi không thích sự trùng lặp liên quan đến diễn viên +. Tôi thực sự muốn một loại cấu trúc "như thể nếu" thực hiện cả hai hành động trong một. Họ đi cùng nhau thường xuyên ...
Jon Skeet

2
@Jon Skeet: xin lỗi vì sự muộn màng của tôi. Tôi và Cast: 2135, Is And As: 2145, As And null check: 1961, thông số kỹ thuật: OS: Windows Seven, CPU: i5-520M, 4GB DDR3 1033 ram, điểm chuẩn trên mảng trong số 128.000.000 mặt hàng.
Behrooz

2
Với C # 7, bạn có thể thực hiện: if (randomObject is TargetType convertedRandomObject){ // Do stuff with convertedRandomObject.Value}hoặc sử dụng switch/ case xem tài liệu
WerWet

72

"as" sẽ trả về NULL nếu không thể truyền.

đúc trước sẽ đưa ra một ngoại lệ.

Đối với hiệu suất, nâng cao một ngoại lệ thường tốn kém hơn về thời gian.


3
Việc tăng ngoại lệ sẽ tốn kém hơn, nhưng nếu bạn biết rằng đối tượng có thể được chọn chính xác, cần nhiều thời gian hơn vì kiểm tra an toàn (xem phản hồi của Anton). Tuy nhiên, chi phí kiểm tra an toàn là, tôi tin rằng, khá nhỏ.

17
Chi phí có khả năng gây ra một ngoại lệ là một yếu tố cần xem xét, nhưng nó thường là thiết kế chính xác.
Jeffrey L Whitledge

@panesoflass - Đối với các loại tham chiếu, khả năng tương thích chuyển đổi sẽ luôn được kiểm tra tại thời điểm chạy cho cả dạng và dạng, do đó, yếu tố đó sẽ không phân biệt giữa hai tùy chọn. (Nếu điều này không phải như vậy, thì diễn viên không thể đưa ra một ngoại lệ.)
Jeffrey L Whitledge

4
@Frank - Ví dụ: nếu bạn được yêu cầu sử dụng bộ sưu tập tiền chung, và một phương thức trong API của bạn yêu cầu danh sách Nhân viên và một số người pha trò thay vì vượt qua danh sách Sản phẩm, thì ngoại lệ không hợp lệ có thể phù hợp để báo hiệu việc vi phạm các yêu cầu giao diện.
Jeffrey L Whitledge

27

Đây là một câu trả lời khác, với một số so sánh IL. Hãy xem xét lớp học:

public class MyClass
{
    public static void Main()
    {
        // Call the 2 methods
    }

    public void DirectCast(Object obj)
    {
        if ( obj is MyClass)
        { 
            MyClass myclass = (MyClass) obj; 
            Console.WriteLine(obj);
        } 
    } 


    public void UsesAs(object obj) 
    { 
        MyClass myclass = obj as MyClass; 
        if (myclass != null) 
        { 
            Console.WriteLine(obj);
        } 
    }
}

Bây giờ hãy nhìn vào IL mỗi phương thức sản xuất. Ngay cả khi các mã op không có ý nghĩa gì với bạn, bạn có thể thấy một sự khác biệt lớn - isinst đang được gọi theo sau bởi castgroup trong phương thức DirectCast. Vì vậy, hai cuộc gọi thay vì một cuộc gọi cơ bản.

.method public hidebysig instance void  DirectCast(object obj) cil managed
{
  // Code size       22 (0x16)
  .maxstack  8
  IL_0000:  ldarg.1
  IL_0001:  isinst     MyClass
  IL_0006:  brfalse.s  IL_0015
  IL_0008:  ldarg.1
  IL_0009:  castclass  MyClass
  IL_000e:  pop
  IL_000f:  ldarg.1
  IL_0010:  call       void [mscorlib]System.Console::WriteLine(object)
  IL_0015:  ret
} // end of method MyClass::DirectCast

.method public hidebysig instance void  UsesAs(object obj) cil managed
{
  // Code size       17 (0x11)
  .maxstack  1
  .locals init (class MyClass V_0)
  IL_0000:  ldarg.1
  IL_0001:  isinst     MyClass
  IL_0006:  stloc.0
  IL_0007:  ldloc.0
  IL_0008:  brfalse.s  IL_0010
  IL_000a:  ldarg.1
  IL_000b:  call       void [mscorlib]System.Console::WriteLine(object)
  IL_0010:  ret
} // end of method MyClass::UsesAs

Từ khóa isinst so với castgroup

Bài đăng blog này có một so sánh tốt giữa hai cách làm. Tóm tắt của ông là:

  • Trong một so sánh trực tiếp, isinst nhanh hơn so với castgroup (mặc dù chỉ một chút)
  • Khi phải thực hiện kiểm tra để đảm bảo chuyển đổi thành công, isinst nhanh hơn đáng kể so với castgroup
  • Không nên sử dụng kết hợp isinst và castgroup vì tốc độ này chậm hơn nhiều so với chuyển đổi "an toàn" nhanh nhất (chậm hơn 12%)

Cá nhân tôi luôn sử dụng As, vì nó dễ đọc và được nhóm phát triển .NET (hoặc Jeffrey Richter khuyên dùng).


Tôi đang tìm kiếm lời giải thích rõ ràng cho việc đúc so với, câu trả lời này làm cho nó rõ ràng hơn vì nó liên quan đến ngôn ngữ trung gian thông thường từng bước giải thích. Cảm ơn!
Morse

18

Một trong những khác biệt tinh tế hơn giữa hai loại này là từ khóa "as" không thể được sử dụng để truyền khi có một toán tử cast:

public class Foo
{
    public string Value;

    public static explicit operator string(Foo f)
    {
        return f.Value;
    }

}

public class Example
{
    public void Convert()
    {
        var f = new Foo();
        f.Value = "abc";

        string cast = (string)f;
        string tryCast = f as string;
    }
}

Điều này sẽ không biên dịch (mặc dù tôi nghĩ rằng nó đã làm trong các phiên bản trước) trên dòng cuối cùng vì các từ khóa "như" không đưa các toán tử cast vào tài khoản. Các dòng string cast = (string)f;hoạt động tốt mặc dù.


12

như không bao giờ ném một ngoại lệ nếu nó không thể thực hiện việc chuyển đổi trở rỗng thay vì ( như hoạt động trên chỉ các loại tài liệu tham khảo). Vì vậy, sử dụng như là cơ bản tương đương với

_myCls2 = _myObj is MyClass ? (MyClass)_myObj : null;

Mặt khác, phôi kiểu C, ném một ngoại lệ khi không thể chuyển đổi.


4
Tương đương, có, nhưng không giống nhau. Điều này tạo ra nhiều mã hơn sau đó là.
plinth

10

Không thực sự là một câu trả lời cho câu hỏi của bạn, nhưng những gì tôi nghĩ là một điểm liên quan quan trọng.

Nếu bạn đang lập trình đến một giao diện, bạn không cần phải truyền. Hy vọng những diễn viên này rất hiếm. Nếu không bạn có thể cần phải suy nghĩ lại một số giao diện của bạn.


Việc phân vai, cho đến nay, chủ yếu là cần thiết cho Bài kiểm tra đơn vị của tôi nhưng cảm ơn bạn đã đưa nó lên. Tôi sẽ giữ nó trong tâm trí của tôi trong khi tôi làm việc này.
Frank V

Đồng ý với con cóc, tôi cũng tò mò tại sao khía cạnh thử nghiệm đơn vị có liên quan đến việc đúc cho bạn @Frank V. Ở đâu cần đúc, thường cần phải thiết kế lại hoặc tái cấu trúc vì nó cho thấy bạn đang thử để đánh giày các vấn đề khác nhau, nơi chúng nên được quản lý khác nhau.
Thượng nghị sĩ

@TheSenator Câu hỏi này đã hơn 3 tuổi nên tôi không thực sự nhớ. Nhưng tôi có lẽ đã tích cực sử dụng các giao diện ngay cả khi thử nghiệm đơn vị. Có thể bởi vì tôi đang sử dụng mẫu nhà máy và không có quyền truy cập vào một nhà xây dựng công cộng trên các đối tượng mục tiêu để kiểm tra.
Frank V

9

Vui lòng bỏ qua lời khuyên của Jon Skeet, re: tránh mô hình thử nghiệm, ví dụ:

if (randomObject is TargetType)
{
    TargetType foo = randomObject as TargetType;
    // Do something with foo
}

Ý tưởng rằng cái này có giá cao hơn một diễn viên và một bài kiểm tra null là một MYTH :

TargetType convertedRandomObject = randomObject as TargetType;
if (convertedRandomObject != null)
{
    // Do stuff with convertedRandomObject
}

Đó là một tối ưu hóa vi mô không hoạt động. Tôi đã thực hiện một số thử nghiệm thực tế và thử nghiệm và thử nghiệm thực sự nhanh hơn so với so sánh giữa diễn viên và không, và nó cũng an toàn hơn vì bạn không có khả năng có một tham chiếu null trong phạm vi bên ngoài nếu diễn viên nên Thất bại.

Nếu bạn muốn một lý do tại sao thử nghiệm và diễn viên nhanh hơn, hoặc ít nhất là không chậm hơn, có một lý do đơn giản và phức tạp.

Đơn giản: ngay cả các trình biên dịch ngây thơ sẽ kết hợp hai hoạt động tương tự, như thử nghiệm và phân vai, thành một thử nghiệm và một nhánh. cast-and-null-test có thể buộc hai thử nghiệm và một nhánh, một cho thử nghiệm loại và chuyển đổi thành null khi thất bại, một cho chính kiểm tra null. Ít nhất, cả hai sẽ tối ưu hóa thành một thử nghiệm và một nhánh, do đó, thử nghiệm và phân vai sẽ không chậm hơn hay nhanh hơn so với thử nghiệm không và thử nghiệm.

Phức tạp: tại sao test-and cast lại nhanh hơn: cast-and-null-test đưa một biến khác vào phạm vi bên ngoài mà trình biên dịch phải theo dõi để sinh động và nó có thể không thể tối ưu hóa biến đó tùy thuộc vào mức độ phức tạp của điều khiển của bạn- dòng chảy là. Ngược lại, test-and-cast giới thiệu một biến mới chỉ trong phạm vi được phân tách để trình biên dịch biết rằng biến đó đã chết sau khi thoát phạm vi và do đó có thể tối ưu hóa phân bổ đăng ký tốt hơn.

Vì vậy, xin vui lòng, xin vui lòng để lời khuyên "thử nghiệm và thử nghiệm" này tốt hơn lời khuyên thử nghiệm và thử nghiệm "DIE. XIN VUI LÒNG. kiểm tra và đúc là an toàn hơn và nhanh hơn.


7
@naasking: Nếu bạn kiểm tra hai lần (theo đoạn trích đầu tiên của bạn), có khả năng loại sẽ thay đổi giữa hai lần kiểm tra, nếu đó là trường hoặc reftham số. Nó an toàn cho các biến cục bộ, nhưng không phải cho các trường. Tôi sẽ quan tâm đến việc chạy điểm chuẩn của bạn, nhưng mã bạn đã đưa ra trong bài đăng trên blog của bạn chưa hoàn tất. Tôi đồng ý với việc không tối ưu hóa vi mô, nhưng tôi không nghĩ việc sử dụng giá trị hai lần là dễ đọc hoặc thanh lịch hơn so với sử dụng "như" và kiểm tra vô hiệu. (Tôi chắc chắn sẽ sử dụng một dàn diễn viên thẳng chứ không phải "như" sau một, btw.)
Jon Skeet

5
Tôi cũng không thấy tại sao nó an toàn hơn. Trên thực tế, tôi đã chỉ ra tại sao nó kém an toàn hơn . Chắc chắn, bạn kết thúc với một biến trong phạm vi có thể là null, nhưng trừ khi bạn bắt đầu sử dụng biến đó ngoài phạm vi của khối "nếu" tiếp theo, bạn vẫn ổn. Mối quan tâm về an toàn mà tôi đã nêu ra (xung quanh các trường thay đổi giá trị của chúng) là mối quan tâm thực sự với mã được hiển thị - mối quan tâm an toàn của bạn yêu cầu các nhà phát triển phải lỏng lẻo trong các mã khác.
Jon Skeet

1
+1 để chỉ ra rằng / cast hoặc as / cast không chậm hơn trong thực tế, làm phiền bạn. Sau khi chạy một thử nghiệm hoàn chỉnh bản thân mình, tôi có thể khẳng định nó làm cho không có sự khác biệt như xa như tôi có thể nhìn thấy - và thẳng thắn, bạn có thể chạy một boggling số phôi trong một thời gian rất nhỏ. Sẽ cập nhật câu trả lời của tôi với mã đầy đủ.
Jon Skeet

1
Thật vậy, nếu ràng buộc không phải là cục bộ thì có khả năng xảy ra lỗi TOCTTOU (thời gian kiểm tra đến thời gian sử dụng), vì vậy điểm tốt ở đó. Về lý do tại sao nó an toàn hơn, tôi làm việc với rất nhiều nhà phát triển cơ sở muốn sử dụng lại người dân địa phương vì một số lý do. Do đó, cast-and-null là mối nguy hiểm rất thực trong trải nghiệm của tôi và tôi chưa bao giờ gặp phải tình huống TOCTTOU vì tôi không thiết kế mã theo cách đó. Đối với tốc độ kiểm tra thời gian chạy, nó thậm chí còn nhanh hơn cả công văn ảo [1]! Re: code, tôi sẽ xem liệu tôi có thể tìm được nguồn cho bài kiểm tra diễn viên không. [1] elevlogics.blogspot.com/2008/10/ khăn
naasking

1
@naasking: Tôi chưa bao giờ gặp phải vấn đề tái sử dụng cục bộ - nhưng tôi nói rằng việc phát hiện mã dễ dàng hơn so với lỗi TOCTTOU tinh tế hơn. Cũng đáng để chỉ ra rằng tôi vừa chạy lại kiểm tra điểm chuẩn của riêng mình cho các giao diện thay vì một lớp niêm phong và lời khuyên đó là hiệu suất có lợi cho việc kiểm tra as-then-null ... nhưng như tôi đã nói, hiệu suất không phải là Tại sao tôi chọn bất kỳ phương pháp cụ thể nào ở đây.
Jon Skeet

4

Nếu diễn viên thất bại, từ khóa 'as' sẽ không ném ngoại lệ; thay vào đó, nó đặt biến thành null (hoặc thành giá trị mặc định của nó cho các loại giá trị).


3
Không có giá trị mặc định cho các loại giá trị. Như không thể được sử dụng để đúc các loại giá trị.
Patrik Hägne

2
Từ khóa 'as' thực sự không hoạt động trên các loại giá trị, vì vậy nó luôn được đặt thành null.
Erik van Brakel

4

Đây không phải là một câu trả lời cho câu hỏi mà là nhận xét về ví dụ mã của câu hỏi:

Thông thường, bạn không cần phải truyền Object từ ví dụ IMyInterface sang MyClass. Điều tuyệt vời về giao diện là nếu bạn lấy một đối tượng làm đầu vào thực hiện giao diện, thì bạn không cần phải quan tâm loại đối tượng nào bạn sẽ nhận được.

Nếu bạn sử dụng IMyInterface cho MyClass, bạn đã cho rằng bạn nhận được một đối tượng thuộc loại MyClass và sẽ không có ý nghĩa gì khi sử dụng IMyInterface, bởi vì nếu bạn cung cấp mã của mình với các lớp khác thực hiện IMyInterface, nó sẽ phá vỡ mã của bạn ...

Bây giờ, lời khuyên của tôi: nếu giao diện của bạn được thiết kế tốt, bạn có thể tránh được rất nhiều lỗi đánh máy.


3

Các asnhà điều hành chỉ có thể được sử dụng trên các loại tài liệu tham khảo, nó không thể bị quá tải, và nó sẽ trở lạinull nếu hoạt động không thành. Nó sẽ không bao giờ ném một ngoại lệ.

Đúc có thể được sử dụng trên bất kỳ loại tương thích nào, nó có thể bị quá tải và nó sẽ ném ngoại lệ nếu thao tác thất bại.

Sự lựa chọn để sử dụng phụ thuộc vào hoàn cảnh. Chủ yếu, vấn đề là bạn có muốn ném ngoại lệ vào một chuyển đổi không thành công hay không.


1
'as' cũng có thể được sử dụng trên các loại giá trị nullable, cung cấp một mẫu thú vị. Xem câu trả lời của tôi cho mã.
Jon Skeet

1

Câu trả lời của tôi chỉ là về tốc độ trong các trường hợp khi chúng tôi không kiểm tra loại và chúng tôi không kiểm tra null sau khi truyền. Tôi đã thêm hai bài kiểm tra bổ sung vào mã của Jon Skeet:

using System;
using System.Diagnostics;

class Test
{
    const int Size = 30000000;

    static void Main()
    {
        object[] values = new object[Size];

        for (int i = 0; i < Size; i++)
        {
            values[i] = "x";
        }
        FindLengthWithIsAndCast(values);
        FindLengthWithIsAndAs(values);
        FindLengthWithAsAndNullCheck(values);

        FindLengthWithCast(values);
        FindLengthWithAs(values);

        Console.ReadLine();
    }

    static void FindLengthWithIsAndCast(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            if (o is string)
            {
                string a = (string)o;
                len += a.Length;
            }
        }
        sw.Stop();
        Console.WriteLine("Is and Cast: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }

    static void FindLengthWithIsAndAs(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            if (o is string)
            {
                string a = o as string;
                len += a.Length;
            }
        }
        sw.Stop();
        Console.WriteLine("Is and As: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }

    static void FindLengthWithAsAndNullCheck(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            string a = o as string;
            if (a != null)
            {
                len += a.Length;
            }
        }
        sw.Stop();
        Console.WriteLine("As and null check: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }
    static void FindLengthWithCast(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            string a = (string)o;
            len += a.Length;
        }
        sw.Stop();
        Console.WriteLine("Cast: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }

    static void FindLengthWithAs(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            string a = o as string;
            len += a.Length;
        }
        sw.Stop();
        Console.WriteLine("As: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }
}

Kết quả:

Is and Cast: 30000000 : 88
Is and As: 30000000 : 93
As and null check: 30000000 : 56
Cast: 30000000 : 66
As: 30000000 : 46

Đừng cố tập trung vào tốc độ (như tôi đã làm) vì tất cả điều này rất rất nhanh.


Tương tự, trong thử nghiệm của tôi, tôi thấy rằng aschuyển đổi (không có kiểm tra lỗi) chạy nhanh hơn khoảng 1-3% so với truyền (khoảng 540ms so với 550ms trên 100 triệu lần lặp). Không phải sẽ làm hoặc phá vỡ ứng dụng của bạn.
palswim

1

Bên cạnh tất cả những gì đã được phơi bày ở đây, tôi chỉ bắt gặp một sự khác biệt thực tế mà tôi nghĩ là đáng chú ý, giữa các vai diễn rõ ràng

var x = (T) ...

so với việc sử dụng as toán tử.

Dưới đây là ví dụ:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(GenericCaster<string>(12345));
        Console.WriteLine(GenericCaster<object>(new { a = 100, b = "string" }) ?? "null");
        Console.WriteLine(GenericCaster<double>(20.4));

        //prints:
        //12345
        //null
        //20.4

        Console.WriteLine(GenericCaster2<string>(12345));
        Console.WriteLine(GenericCaster2<object>(new { a = 100, b = "string" }) ?? "null");

        //will not compile -> 20.4 does not comply due to the type constraint "T : class"
        //Console.WriteLine(GenericCaster2<double>(20.4));
    }

    static T GenericCaster<T>(object value, T defaultValue = default(T))
    {
        T castedValue;
        try
        {
            castedValue = (T) Convert.ChangeType(value, typeof(T));
        }
        catch (Exception)
        {
            castedValue = defaultValue;
        }

        return castedValue;
    }

    static T GenericCaster2<T>(object value, T defaultValue = default(T)) where T : class
    {
        T castedValue;
        try
        {
            castedValue = Convert.ChangeType(value, typeof(T)) as T;
        }
        catch (Exception)
        {
            castedValue = defaultValue;
        }

        return castedValue;
    }
}

Dòng dưới cùng: GenericCaster2 sẽ không hoạt động với các loại cấu trúc. GenericCaster sẽ.


1

Nếu bạn sử dụng Office PIA nhắm mục tiêu .NET Framework 4.X, bạn nên sử dụng từ khóa dưới dạng , nếu không nó sẽ không được biên dịch.

Microsoft.Office.Interop.Outlook.Application o = new Microsoft.Office.Interop.Outlook.Application();
Microsoft.Office.Interop.Outlook.MailItem m = o.CreateItem(Microsoft.Office.Interop.Outlook.OlItemType.olMailItem) as Microsoft.Office.Interop.Outlook.MailItem;

Truyền vẫn ổn khi nhắm mục tiêu .NET 2.0:

Microsoft.Office.Interop.Outlook.MailItem m = (Microsoft.Office.Interop.Outlook.MailItem)o.CreateItem(Microsoft.Office.Interop.Outlook.OlItemType.olMailItem);

Khi nhắm mục tiêu .NET 4.X, các lỗi là:

lỗi CS0656: Thiếu trình biên dịch yêu cầu thành viên 'Microsoft.CSharp.R nbBinder.Binder.Convert'

lỗi CS0656: Thiếu trình biên dịch yêu cầu thành viên 'Microsoft.CSharp.R nbBinder.CSharpArgumentInfo.Create'


0

Các astừ khóa hoạt động giống như một diễn viên rõ ràng giữa các loại tài liệu tham khảo phù hợp với sự khác biệt lớn mà nó không làm tăng một ngoại lệ nếu chuyển đổi thất bại. Thay vào đó, nó mang lại một giá trị null trong biến mục tiêu. Vì Ngoại lệ rất tốn kém về hiệu suất, nó được coi là một phương pháp đúc tốt hơn nhiều.


Không giống nhau, như một cuộc gọi CastClass và các cuộc gọi khác IsInst trong mã IL.
Jenix

0

Những gì bạn chọn mạnh mẽ phụ thuộc vào những gì cần thiết. Tôi thích đúc rõ ràng

IMyInterface = (IMyInterface)someobj;

bởi vì nếu đối tượng nên thuộc loại IMyInterface và nó không phải - đó chắc chắn là vấn đề. Tốt hơn là nhận lỗi càng sớm càng tốt vì lỗi chính xác sẽ được sửa thay vì sửa tác dụng phụ của nó.

Nhưng nếu bạn xử lý các phương thức chấp nhận objectlàm tham số thì bạn cần kiểm tra loại chính xác của nó trước khi thực hiện bất kỳ mã nào. Trong trường hợp như vậy assẽ hữu ích để bạn có thể tránh InvalidCastException.


0

Nó phụ thuộc, bạn có muốn kiểm tra null sau khi sử dụng "as" hoặc bạn muốn ứng dụng của mình đưa ra một ngoại lệ?

Nguyên tắc nhỏ của tôi là nếu tôi luôn mong muốn biến đó là loại tôi đang mong đợi tại thời điểm tôi muốn tôi sử dụng một diễn viên. Nếu có thể biến đó sẽ không chuyển sang những gì tôi muốn và tôi sẵn sàng xử lý null từ việc sử dụng as, tôi sẽ sử dụng như.



0

Vấn đề của OP bị giới hạn trong một tình huống đúc cụ thể. Tiêu đề bao gồm nhiều tình huống hơn.
Dưới đây là tổng quan về tất cả các tình huống truyền có liên quan mà tôi hiện có thể nghĩ đến:

private class CBase
{
}

private class CInherited : CBase
{
}

private enum EnumTest
{
  zero,
  one,
  two
}

private static void Main (string[] args)
{
  //########## classes ##########
  // object creation, implicit cast to object
  object oBase = new CBase ();
  object oInherited = new CInherited ();

  CBase oBase2 = null;
  CInherited oInherited2 = null;
  bool bCanCast = false;

  // explicit cast using "()"
  oBase2 = (CBase)oBase;    // works
  oBase2 = (CBase)oInherited;    // works
  //oInherited2 = (CInherited)oBase;   System.InvalidCastException
  oInherited2 = (CInherited)oInherited;    // works

  // explicit cast using "as"
  oBase2 = oBase as CBase;
  oBase2 = oInherited as CBase;
  oInherited2 = oBase as CInherited;  // returns null, equals C++/CLI "dynamic_cast"
  oInherited2 = oInherited as CInherited;

  // testing with Type.IsAssignableFrom(), results (of course) equal the results of the cast operations
  bCanCast = typeof (CBase).IsAssignableFrom (oBase.GetType ());    // true
  bCanCast = typeof (CBase).IsAssignableFrom (oInherited.GetType ());    // true
  bCanCast = typeof (CInherited).IsAssignableFrom (oBase.GetType ());    // false
  bCanCast = typeof (CInherited).IsAssignableFrom (oInherited.GetType ());    // true

  //########## value types ##########
  int iValue = 2;
  double dValue = 1.1;
  EnumTest enValue = EnumTest.two;

  // implicit cast, explicit cast using "()"
  int iValue2 = iValue;   // no cast
  double dValue2 = iValue;  // implicit conversion
  EnumTest enValue2 = (EnumTest)iValue;  // conversion by explicit cast. underlying type of EnumTest is int, but explicit cast needed (error CS0266: Cannot implicitly convert type 'int' to 'test01.Program.EnumTest')

  iValue2 = (int)dValue;   // conversion by explicit cast. implicit cast not possible (error CS0266: Cannot implicitly convert type 'double' to 'int')
  dValue2 = dValue;
  enValue2 = (EnumTest)dValue;  // underlying type is int, so "1.1" beomces "1" and then "one"

  iValue2 = (int)enValue;
  dValue2 = (double)enValue;
  enValue2 = enValue;   // no cast

  // explicit cast using "as"
  // iValue2 = iValue as int;   error CS0077: The as operator must be used with a reference type or nullable type
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.