o3應該是引用類型了吧,為什麼我改變o3的值,o4沒有改變?難道object是值類型?---------------------------------------------------更新---------------------------------------------------

using System;
namespace ObjeNS{
public class ObjCs{
static void Main(string[] args){
object valueType1 = 213;
object valueType2 = valueType1;
Console.WriteLine("value1: {0}, value2: {1}", valueType1, valueType2); //=&> value1: 213, value2: 213
valueType1 = 233;
Console.WriteLine("value1: {0}, value2: {1}", valueType1, valueType2); //=&> value1: 233, value2: 213

Console.WriteLine("
");
object referenceType1 = new A(213);
object referenceType2 = referenceType1;
Console.WriteLine("value1: {0}, value2: {1}", referenceType1, referenceType2); //213, 213
((A)referenceType1).a = 233;
Console.WriteLine("value1: {0}, value2: {1}", referenceType1, referenceType2); //233, 233

Console.ReadKey();
}
}
public class A{
public int a;
public A(int val){ this.a = val; }
public override string ToString(){
return "this.a is " + this.a;
}
}
}

這個怎麼破?


object是隻讀對象,引用類型。

我真心覺得初學者完全沒必要搞清楚引用類型和值類型的區別,畢竟堆棧是啥都沒弄明白前,不清楚聲明類型和運行時類型什麼區別前,怎麼去理解引用類型和值類型的區別在於編譯後的運行時行為。

混淆只讀對象和值類型是最容易犯的錯誤之一。只讀對象根本沒有辦法通過改變狀態來觀察到引用類型的行為,事實上用對象改變同一引用的另一對象改變來定義引用類型也是錯誤的,引用類型和值類型的定義是運行時行為(而這關係到了使用引用類型還是值類型的決策)。搞不懂就背書,從ValueType繼承的除去Enum都是值類型,其餘的都不是。

因為很多面試官喜歡考這個,所以背背就好了。

好吧我就多羅嗦幾句吧,要真正理解透徹引用類型和值類型必須要到CLR的行為層面。這裡只簡單的說說什麼是隻讀對象,以及怎麼用代碼去驗證引用類型。

只讀對象就是指沒有辦法修改其狀態的對象(也就是說沒有欄位,或者沒有公開的修改欄位的方法或屬性以及事件)。

例如new object(),這就是一個只讀對象,"abc"也是隻讀對象。

用修改對象的狀態來觀察是引用類型還是值類型這一方法對於只讀對象是無效的,因為只讀對象的狀態沒有辦法被修改。

正確的修改對象狀態來觀察引用類型的方式是這樣的:

int[] array = new int[]{ 1, 2, 3 };
var array1 = array;
Console.WriteLine( string.Join( ",", array1 ) );
array[0] = 5;
Console.WriteLine( string.Join( ",", array1 ) );

int[],即數組類型,是一個典型的引用類型,其狀態,即數組元素是可以修改的。所以通過修改數組元素,我們就能觀察到引用類型的特性。

而值類型,絕大多數值類型都被設計為只讀對象,這其實是值類型的設計原則之一。除非我們構建一個不符合設計原則的值類型,否則我們觀察不到賦值的時候值類型被複制的行為。

這也是我比較反感用狀態變化來說明引用類型和值類型以及非要給初學者強調引用類型和值類型區別的原因。我給初學者的建議就是沒事別自己弄個struct,除非你已經完全明白你在幹嗎

那麼不自定義值類型,能不能觀察到值類型與引用類型的行為不同呢?

可以。

{
var obj1 = new object();
var obj2 = obj1;

Console.WriteLine( object.ReferenceEquals( obj1, obj2 ) );
}

{
var obj1 = new int();
var obj2 = obj1;

Console.WriteLine( object.ReferenceEquals( obj1, obj2 ) );
}

這是比較好的判斷值類型和引用類型的方法,不過原理卻不是因為值類型在賦值的時候被複制了,而是因為在ReferenceEquals的時候會被裝箱(裝箱就是運行時行為的一個不同,只有值類型會導致裝箱,引用類型不會導致裝箱)。

大家不妨猜一下下面這段代碼結果是什麼:

{
var obj1 = new int();

Console.WriteLine( object.ReferenceEquals( obj1, obj1 ) );
}


你把自己搞糊塗了而已。

string s1 = "hello";
string s2 = s1;
s1 = "world"
Console.WriteLine("{0} {1}", s1, s2);
// 運行結果是「world hello」,難道string是值類型?

再看下面這個例子就知道你錯在哪了

class A {
public int a;
}
A a1 = new A();
a1.a = 1;
A a2 = a1;
a1 = new A();
a1.a = 2;
Console.WriteLine("{0} {1}", a1.a, a2.a);
// 運行結果2 1,難道A也是值類型?
// 顯然事實是你弄錯了,下面這種代碼才說明A是引用類型
A a3 = a1;
a1.a = 3;
Console.WriteLine("{0} {1}", a1.a, a2.a);
// 運行結果3 3,A終於是引用類型了

所以說,上面的"object是值類型","string是值類型"的困惑,完全是因為object和string是不可變對象

new object()是類似空集合的不可變對象string是CLR特意設定出來的不可變對象
這個問題我今天看書突然想起來~那麼就從我的看法來談談吧!首先,我認為object是引用類型。首先,從元數據來看從元數據來看,很明顯是class,而非struct。然後,在ArrayList非泛型方法進行操作時,如果直接傳入一個struct,便引發了裝箱。他的本質來說就是將一個struct轉換成為一個object進行傳入,那如果object是值類型,那不是要進行複製,並且不會涉及到裝箱麼?最後,struct有一個極為重要的特性:不可繼承!但是!C#所有類型都是繼承自Object類的。綜上所述,我認為Object是引用類型,不是值類型。

而題主的實驗也做得著實很有問題~你造嗎~你第一句new Object(),已經在你不知不覺間被GC帶走了~

大大 @趙劼的回答誤導性很大,大大開這樣的玩笑我覺得不妥~嘿嘿
這個測試方法有問題. 第一行 o3=new object() ..., 第二行o3= "test", "test"是一個字元串對象,這個賦值操作已經把第一行的object對象丟棄了. 同樣o3="changed"又指向了一個新對象.如果要測試,因該測用"."操作符測試,只是object可能沒有暴露什麼可變狀態的方法.
Object是引用類型,至於實驗結果為什麼會是這樣,看下圖解釋----------------------------------------------------------------------------------------------------------------------------考慮到Alan提出的困惑,我對賦值"="作下解釋:"="不管對於值類型還是引用類型,做的都是一份拷貝,只不對對於值類型拷貝的是數據本身,而引用拷貝的是其數據的引用地址,所以object o4 = o3;就出現了上圖的解釋
Simply,object其實是System.Object,它是個class,也就是說,是引用類型。

題主的例子並不「科學」,更詳細的信息請看這裡:C# Object/object


o3是對new object()的內存地址A記錄,o4=o3;是讓o4記錄地址A,此時為o4="test",接著你又開內存給"changed",並讓o3記錄"changed"的內存地址,到此時候,o3和o4分別記錄著(對應著)不同的內存地址,就相當於你幹了這事:

object o3="changed";

object o4="test";

從垃圾回收的角度說,還有引用計數的問題。


沒錯呀,o3="changed"只是將另一份string賦給o3了,並沒有改變o3原先指定的值還有string是不可變的,不可能被改變
一開始o3,o4都指向"test",改變o3的指向不會改變o4的指向,沒錯啊

//下面結論待定啊。。。。還是沒太懂,先不糾結這個了!

特別感謝各位的回答,趙大牛好像說的太深奧了(看過趙大牛的WebCast,表示崇拜趙大牛)?

總上所評,我自己有試了一下,覺得object不是不可變對象(只讀對象),string倒是是不可變對象,我問題裏的例子,碰巧將string類型賦給object,表現出不可變對象的行為。[個人觀點而已,可能有理解錯誤的地方]


下面拿@sixue 童鞋的例子說一下(我加了兩個方法)

A a1 = new A(1);

A a2 = a1;

Console.WriteLine("a1:{0}, a2:{1}", a1.a, a2.a); //=&> a1:1, a2:1

a1.a = 2; //這裡@sixue 童鞋用new A()重新賦值給a1,表現出值類型的行為,我覺得那就等於將a1重新初始化了,跟我問題裏的例子沒共同點。

Console.WriteLine("a1:{0}, a2:{1}", a1.a, a2.a); //=&> a1:2, a2:2

下面拿一個「正常點」的引用類型賦給object,不用不可變對象的string類型了

下面這個栗子就表現出引用類型的行為了

object o1 = new A(1);

object o2 = o1;


Console.WriteLine("o1:{0}, o2:{1}", o1, o2); //=&> o1:1, o2:1

((A)o1).a = 2;

Console.WriteLine("o1:{0}, o2:{1}", o1, o2); //=&> o1:2, o2:2

所以我覺得,是不是趙大牛說的最正確了?

簡而言之,object是引用類型,string是隻讀對象,就如@Ivony 所說如果想讓object表現出不可變對象的行為就使用問題裏的例子,想讓object表現出可變對象的就使用上面的栗子。如果總結錯了,幫忙給糾正下,再次感謝各位網友(@徐東焱 @Ivony @歐陽佳偉 @sixue @Arbing @趙大牛)的解惑!


推薦閱讀:
相關文章