c# is 和 as 淺看重製版

敖毛毛發表於2024-11-30

前言

當年寫的比較差:https://www.cnblogs.com/aoximin/p/12965408.html,所以特來重新寫一遍。

正文

首先為什麼會出現is 和 as 呢?

因為是為了有需要檢驗的地方,如果直接使用顯示轉換的話,那麼可能直接報錯了。

namespace ConsoleApp4
{
    class Program
    {
        static void Main(string[] args)
        {
            object obj = new Student();
            var c = (Teacher)obj;
        }

        public class Student
        {
            
        }

        public class Teacher
        { 
        }
    }
}

比如這樣肯定會報錯的, 因為在執行的時候我們來檢視一下il語句。

.method private hidebysig static 
	void Main (
		string[] args
	) cil managed 
{
	.custom instance void System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = (
		01 00 01 00 00
	)
	// Method begins at RVA 0x20a4
	// Header size: 12
	// Code size: 15 (0xf)
	.maxstack 1
	.entrypoint
	.locals init (
		[0] object obj,
		[1] class ConsoleApp4.Program/Teacher c
	)

	IL_0000: nop
	IL_0001: newobj instance void ConsoleApp4.Program/Student::.ctor()
	IL_0006: stloc.0
	IL_0007: ldloc.0
	IL_0008: castclass ConsoleApp4.Program/Teacher
	IL_000d: stloc.1
	IL_000e: ret
} // end of method Program::Main

會呼叫castclass 進行轉換。

因為castclass 無法找到他們兩個的轉換方法(編寫顯示轉換或者隱式轉換的方法),他們也不是繼承關係,所以會丟擲異常。

但是也不要以為所以的強制轉換就一定要編寫啥編寫顯示轉換或者隱式轉換的方法或者是啥繼承關係,還有一種是編譯器行為。

比如說:

long a = 1;
int b = (int)a;

編譯出來的程式碼是:

IL_000d: stloc.1
IL_000e: ldc.i4.1
IL_000f: conv.i8
IL_0010: stloc.2
IL_0011: ldloc.2
IL_0012: conv.i4
IL_0013: stloc.3

這就是編譯器的行為了,編譯器認為自己可以處理就不報錯了,直接截斷作為處理了。

好吧,不能走的太遠了,回到is 上。

那麼is就可以避免一些執行時候的報錯,而不需要用try catch 這種不太優雅的方式。

但是呢,is 是無法去檢查自己編寫的顯示轉換或隱式轉換的方法,請看VAR:

class Program
{
	static void Main(string[] args)
	{
		object obj = new Student();
		if (obj is Teacher)
		{
			var c = (Teacher)obj;
		}
	}

	public class Student
	{
		
	}

	public class Teacher
	{ 
	}
}

編譯出來呢?是下面這樣:

.method private hidebysig static 
	void Main (
		string[] args
	) cil managed 
{
	.custom instance void System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = (
		01 00 01 00 00
	)
	// Method begins at RVA 0x20a4
	// Header size: 12
	// Code size: 30 (0x1e)
	.maxstack 2
	.entrypoint
	.locals init (
		[0] object obj,
		[1] bool,
		[2] class ConsoleApp4.Program/Teacher c
	)

	IL_0000: nop
	IL_0001: newobj instance void ConsoleApp4.Program/Student::.ctor()
	IL_0006: stloc.0
	IL_0007: ldloc.0
	IL_0008: isinst ConsoleApp4.Program/Teacher
	IL_000d: ldnull
	IL_000e: cgt.un
	IL_0010: stloc.1
	IL_0011: ldloc.1
	IL_0012: brfalse.s IL_001d

	IL_0014: nop
	IL_0015: ldloc.0
	IL_0016: castclass ConsoleApp4.Program/Teacher
	IL_001b: stloc.2
	IL_001c: nop

	IL_001d: ret
} // end of method Program::Main

is 轉換為il程式碼就是isinst

這個只能判斷繼承關係,所以嘛,這個呢,其實也能理解,如果是自己編寫了轉換方法,哪裡自己不知道還要is呢。

那麼為啥會出現as呢?

還是效能問題嘛。

v1:

static void Main(string[] args)
{
	object obj = new Student();
	if (obj is Teacher)
	{
		var c = (Teacher)obj;
	}
}

v2:

static void Main(string[] args)
{
	object obj = new Student();
	var c = obj as Teacher;
}

public class Student
{
	
}

public class Teacher
{ 
}

這兩者有啥區別呢?道理上執行結果都一致。

但是呢,v2 更優,因為v2檢查了一次,而v1檢查來了兩次。

v2的il程式碼:

.method private hidebysig static 
	void Main (
		string[] args
	) cil managed 
{
	.custom instance void System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = (
		01 00 01 00 00
	)
	// Method begins at RVA 0x20a4
	// Header size: 12
	// Code size: 15 (0xf)
	.maxstack 1
	.entrypoint
	.locals init (
		[0] object obj,
		[1] class ConsoleApp4.Program/Teacher c
	)

	IL_0000: nop
	IL_0001: newobj instance void ConsoleApp4.Program/Student::.ctor()
	IL_0006: stloc.0
	IL_0007: ldloc.0
	IL_0008: isinst ConsoleApp4.Program/Teacher
	IL_000d: stloc.1
	IL_000e: ret
} // end of method Program::Main

這裡也是使用了isinst,因為isinst本身就是檢測並且賦值,再來看下isinst的功效

as 只是執行了stloc.1,彈出賦值給區域性變數,多了一個賦值過程,兩者效能都差不多,所以呢,不要覺得as 好像多比 is 多個一個轉換啥的,其實呼叫的是同一個語句。

只是覺得以前寫的過於潦草,整理下罷了。作業系統篇馬上就要出爐,比較生硬,共128篇,簡單整理一下。

相關文章