数か月前にこの質問をした人がいて、詳細に説明できませんでした。C#の参照型と値型の違いは何ですか?
私は、値の型があることを知っているint
、bool
、float
など、および参照型があるdelegate
、interface
など、またはあまりにも、この間違っていますか?
それを専門的に説明していただけますか?
数か月前にこの質問をした人がいて、詳細に説明できませんでした。C#の参照型と値型の違いは何ですか?
私は、値の型があることを知っているint
、bool
、float
など、および参照型があるdelegate
、interface
など、またはあまりにも、この間違っていますか?
それを専門的に説明していただけますか?
回答:
あなたの例はしばらくので少し奇数でありint
、bool
かつfloat
ある特定のタイプ、インターフェイスとデリゲートである種類のタイプの-同じようにstruct
とenum
値型の種類です。
この記事では、参照型と値型の説明を書きました。私があなたが混乱していると思われるどんなビットでも展開したいです。
「TL; DR」バージョンは、特定のタイプの変数/式の値が何であるかを考えることです。値タイプの場合、値は情報そのものです。参照タイプの場合、値は参照であり、nullの場合もあれば、情報を含むオブジェクトに移動するための方法である場合もあります。
たとえば、変数を一枚の紙のように考えます。値「5」または「false」が書き込まれている可能性がありますが、私の家はありませんでした。私の家への道順がなければなりません。これらの方向は参照に相当します。特に、2人のユーザーが私の家への同じ方向を含む異なる紙片を持っている可能性があります。1人の人がその方向に従って私の家を赤く塗った場合、2人目の人もその変化を見るでしょう。2人とも私の家の写真を紙に書いていただけの場合、1人の人が紙を着色しても、もう1人の紙はまったく変わりません。
while int, bool and float are specific types, interfaces and delegates are kinds of type - just like struct and enum are kinds of value types
。intとはどういう意味ですか。boolは特定の型です。C#のすべて、たとえばint、bool、float、クラス、インターフェース、デリゲートはタイプ(正確にはデータタイプ)です。データ型は、C#では「参照型」と「値型」として分離されます。では、なぜintは特定の型ですが、インターフェイスは一種の型だとお考えですか?
int
は構造体、string
クラス、Action
デリゲートなどです。「int、bool、float、クラス、インターフェース、デリゲート」のリストは、「10、int」と同じように、さまざまな種類のものを含むリストです。さまざまな種類のものが含まれているリスト。
メモリアドレスではなく一部の値を保持します
例:
ストラクト
ストレージ:
TL; DR:変数の値は、それが明確にされている場所に保存されます。ローカル変数は、たとえば、スタックに住んでいるが、それはしっかりとそれがで宣言されたクラスと相まってヒープ上に住んでいるメンバーとしてクラス内で宣言するとき。
長い:それらが宣言されているところはどこでもこのように値型が格納されています。例:int
ローカル変数としての関数内のの値はスタックに格納され、int
クラスのメンバーとして宣言されたin の値は、それが宣言されているクラスとともにヒープに格納されます。値の型クラスのライフタイプは、クラスが宣言されているクラスとまったく同じであり、ガベージコレクターによる作業はほとんど必要ありません。それはもっと複雑ですが、@ JonSkeetの本「C#In Depth」を参照します。より簡潔な説明のための「.NETのメモリ」。
利点:
値型は余分なガベージコレクションを必要としません。住んでいるインスタンスと一緒にガベージコレクションを取得します。メソッドのローカル変数は、メソッドの終了時にクリーンアップされます。
欠点:
値の大きなセットがメソッドに渡されると、受信変数が実際にコピーするため、メモリに2つの冗長な値があります。
クラスが見落とされると、すべてのoopのメリットが失われます
値ではなく値のメモリアドレスを保持します
例:
クラス
ストレージ:
ヒープに格納
利点:
参照変数をメソッドに渡して変更すると、実際に元の値が変更されますが、値型では、指定された変数のコピーが取得され、その値が変更されます。
変数のサイズが大きい場合は参照型が良い
クラスは参照型変数として提供されるため、再利用性を提供し、オブジェクト指向プログラミングにメリットをもたらします
欠点:
ガベージコレクターのvalue.extraオーバーロードを読み取るときの割り当てと逆参照時に参照する作業が増える
コンピュータがどのようにメモリにデータを割り当てるかを知り、ポインタが何であるかを知っている方が、2つの違いを理解するのが簡単だと思いました。
参照は通常、ポインターに関連付けられています。変数が存在するメモリアドレスが実際には、実際のオブジェクトの別のメモリアドレスを別のメモリ位置に保持していることを意味します。
私がこれからしようとしている例は、非常に単純化されているため、1粒の塩でそれを取り上げます。
コンピューターのメモリが(POボックス0001からPOボックスnで始まる)一連の私書箱がその中に何かを保持できると想像してください。私書箱でうまくいかない場合は、ハッシュテーブルや辞書、配列などを試してください。
したがって、次のようなことをすると:
var a = "Hello";
コンピュータは次のことを行います。
値タイプは、そのメモリ位置に実際のものを保持します。
したがって、次のようなことをすると:
変数a = 1;
コンピュータは次のことを行います。
これは、約2年前の別のフォーラムからの投稿です。言語は(C#ではなく)vb.netですが、値型と参照型の概念は.net全体で統一されており、例はそのままです。
また、.net内では、すべてのタイプが技術的には基本タイプのオブジェクトから派生していることを覚えておくことも重要です。値タイプはそのように動作するように設計されていますが、最終的には基本タイプのオブジェクトの機能も継承します。
A.値タイプは、それだけです。それらは、個別のVALUEが格納されるメモリ内の別個の領域を表します。値タイプは固定メモリサイズであり、固定サイズのアドレスのコレクションであるスタックに格納されます。
このようなステートメントを作成すると、次のようになります。
Dim A as Integer
DIm B as Integer
A = 3
B = A
次のことを行いました。
各変数の値は、各メモリ位置に個別に存在します。
B.参照タイプはさまざまなサイズにすることができます。したがって、それらは「スタック」に格納できません(スタックは固定サイズのメモリ割り当てのコレクションですか?)。それらは「マネージヒープ」に格納されます。マネージヒープ上の各アイテムへのポインター(または「参照」)は、スタック(アドレスのように)に保持されます。コードは、スタック内のこれらのポインターを使用して、マネージヒープに格納されているオブジェクトにアクセスします。したがって、コードが参照変数を使用する場合、実際にはポインター(またはマネージヒープ内のメモリの場所への "アドレス")を使用しています。
文字列Property Person.Nameを使用して、clsPersonという名前のクラスを作成したとします。
この場合、次のようなステートメントを作成すると、
Dim p1 As clsPerson
p1 = New clsPerson
p1.Name = "Jim Morrison"
Dim p2 As Person
p2 = p1
上記の場合、p1.Nameプロパティは期待どおりに「Jim Morrison」を返します。p2.Nameプロパティは、直感的に予想できるように、「ジムモリソン」も返します。私は、p1とp2の両方がスタック上の異なるアドレスを表していると思います。ただし、p2にp1の値を割り当てたので、p1とp2の両方がマネージヒープ上のSAME LOCATIONを指します。
この状況を考えてみましょう:
Dim p1 As clsPerson
Dim p2 As clsPerson
p1 = New clsPerson
p1.Name = "Jim Morrison"
p2 = p1
p2.Name = "Janis Joplin"
この状況では、オブジェクトを参照するスタック上のポインターp1を使用して、マネージヒープ上にpersonクラスの新しいインスタンスを1つ作成し、オブジェクトインスタンスのNameプロパティに「Jim Morrison」の値を再度割り当てました。次に、スタック内に別のポインターp2を作成し、p1が参照するものと同じマネージヒープ上の同じアドレスをポイントしました(割り当てp2 = p1を行ったとき)。
ひねりがここに来ます。p2のNameプロパティに値 "Janis Joplin"を割り当てると、次のコードを実行した場合に、p1とp2の両方で参照されるオブジェクトのNameプロパティが変更されます。
MsgBox(P1.Name)
'Will return "Janis Joplin"
MsgBox(p2.Name)
'will ALSO return "Janis Joplin"Because both variables (Pointers on the Stack) reference the SAME OBJECT in memory (an Address on the Managed Heap).
それは意味がありましたか?
最終。これを行う場合:
DIm p1 As New clsPerson
Dim p2 As New clsPerson
p1.Name = "Jim Morrison"
p2.Name = "Janis Joplin"
これで、2つの異なるPersonオブジェクトができました。ただし、これをもう一度実行した分:
p2 = p1
これで両方を「ジムモリソン」にポイントしました。(p2によって参照されるヒープ上のオブジェクトに何が起こったのか正確にはわかりません。.。。スコープから外れたと思います。これは、誰かが私をまっすぐに設定できる領域の1つです。。)-編集:新しい割り当てを行う前に、p2 = Nothing OR p2 = New clsPersonを設定するのはこのためです。
もう一度、今これを行うと:
p2.Name = "Jimi Hendrix"
MsgBox(p1.Name)
MsgBox(p2.Name)
両方のmsgBoxが「Jimi Hendrix」を返すようになりました
これは少し混乱する可能性があります。最後にもう一度言いますが、詳細の一部が間違っている可能性があります。
幸運、そしてうまくいけば、私よりもよく知っている他の人たちがこれを明らかにするのを手伝ってくれるでしょう。。。
値のデータ型と参照データ型
1) 値(データを直接含む)が参照 (データを 参照)
2)における値(すべての変数は独自のコピーを持っている)が、
内基準(変数よりも、いくつかのオブジェクトを参照することができます)
3)値(操作変数は他の変数に影響を与えることはできません)が参照(変数は他の変数に影響を与える可能性があります)
4) 値の型は(int、bool、float)ですが、 参照型は(array、class objects、string)です
「値タイプに基づく変数には直接値が含まれます。ある値タイプ変数を別の値タイプに割り当てると、含まれている値がコピーされます。これは、オブジェクトへの参照をコピーするがオブジェクト自体への参照をコピーしない参照タイプ変数の割り当てとは異なります。」マイクロソフトのライブラリから。
これはおそらく難解な方法で間違っていますが、簡単にするために:
値タイプは、通常「値によって」渡される値です(つまり、値をコピーします)。参照型は「参照によって」渡される(したがって、元の値へのポインタを与える)。.NET ECMA標準では、これらの「もの」がどこに保存されるかについての保証はありません。スタックレスまたはヒープレスの.NETの実装を構築できます(2番目は非常に複雑ですが、おそらくファイバーと多くのスタックを使用して可能です)。
構造体は値の型(int、bool ...は構造体、または少なくとも次のようにシミュレートされます)、クラスは参照型です。
値の型はSystem.ValueTypeから派生します。参照型はSystem.Objectから派生しています。
結局のところ、値の型、「参照オブジェクト」、および参照(C ++ではオブジェクトへのポインタと呼ばれます。.NETでは不透明です。それらが何であるかはわかりません。私たちの観点からすると、オブジェクトへの「ハンドル」です)。これらのラストは値タイプに似ています(コピーで渡されます)。したがって、オブジェクトは、オブジェクト(参照タイプ)とそれに対する0個以上の参照(値タイプと同様)で構成されます。参照がない場合、GCはおそらくそれを収集します。
一般に(.NETの「デフォルト」実装では)、値型はスタック(ローカルフィールドの場合)またはヒープ(クラスのフィールドの場合、イテレータ関数の変数の場合)に配置できます。それらがクロージャーによって参照される変数である場合、それらが非同期関数で(新しいAsync CTPを使用して)変数である場合...)参照値はヒープにのみ送信できます。参照は、値タイプと同じルールを使用します。
値型がイテレータ関数、非同期関数、またはクロージャによって参照されているためにヒープ上にある場合、コンパイルされたファイルを見ると、コンパイラがこれらの変数を配置するクラスを作成していることがわかります。 、関数を呼び出すとクラスが作成されます。
今、私は長いものを書く方法を知りません、そして私は私の人生の中でもっとやるべきことがありました。「正確な」「アカデミック」「正しい」バージョンが必要な場合は、次をお読みください。
http://blogs.msdn.com/b/ericlippert/archive/2010/09/30/the-truth-about-value-types.aspx
探してる15分です!"すぐに使える"記事の要約なので、msdnバージョンよりも優れています。
参照型を考える最も簡単な方法は、参照型を「オブジェクトID」と見なすことです。オブジェクトIDでできることは、1つを作成する、1つをコピーする、1つのタイプを照会または操作する、または2つが等しいかどうかを比較することだけです。オブジェクトIDを使用して他のことを行う試みは、そのIDによって参照されるオブジェクトを使用して、示されたアクションを実行するための省略形と見なされます。
Carタイプの2つの変数XとY(参照タイプ)があるとします。Yはたまたま「オブジェクトID#19531」を保持しています。「X = Y」と言うと、Xは「オブジェクトID#19531」を保持します。XもYも車を持っていないことに注意してください。「オブジェクトID#19531」としても知られる車は、別の場所に保管されます。YをXにコピーすると、ID番号をコピーするだけで済みました。ここで、X.Color = Colors.Blueとするとします。このようなステートメントは、「オブジェクトID#19531」を見つけて青色にペイントするための指示と見なされます。XとYが黄色の車ではなく青い車を参照するようになったとしても、どちらもまだ同じ車である「オブジェクトID#19531」を参照しているため、ステートメントは実際にはXまたはYに影響しません。常にされています。
変数タイプと参照値は適用が簡単で、ドメインモデルに適切に適用できるため、開発プロセスが容易になります。
「値型」の量に関する神話を取り除くために、これがプラットフォームでどのように処理されるかについてコメントします。NET、具体的にはC#(CSharp)でAPISが呼び出されたときに、メソッドと関数で値によって、参照によってパラメーターを送信し、これらの値のパッセージを正しく処理する方法
この記事を読む Cでの変数の型の値と参照#
仮定v
値型表現/変数であり、r
参照型の発現/変数であります
x = v
update(v) //x will not change value. x stores the old value of v
x = r
update(r) //x now refers to the updated r. x only stored a link to r,
//and r can change but the link to it doesn't .
したがって、値タイプの変数には実際の値(5、または "h")が格納されます。参照型変数は、値が存在する比喩的なボックスへのリンクのみを格納します。
C#で使用できるさまざまなデータ型を説明する前に、C#は強く型付けされた言語であることを説明することが重要です。つまり、各変数、定数、入力パラメーター、戻り値の型、および一般に、値に評価されるすべての式には型があります。
各型には、コンパイラによって実行可能ファイルに埋め込まれるメタデータとして、共通言語ランタイム(CLR)が使用する情報が含まれています。これは、メモリを割り当てて再利用するときに型の安全性を保証します。
特定のタイプが割り当てるメモリの量を知りたい場合は、次のようにsizeof演算子を使用できます。
static void Main()
{
var size = sizeof(int);
Console.WriteLine($"int size:{size}");
size = sizeof(bool);
Console.WriteLine($"bool size:{size}");
size = sizeof(double);
Console.WriteLine($"double size:{size}");
size = sizeof(char);
Console.WriteLine($"char size:{size}");
}
出力には、各変数によって割り当てられたバイト数が表示されます。
int size:4
bool size:1
double size:8
char size:2
各タイプに関連する情報は次のとおりです。
タイプに含まれるメンバー(メソッド、フィールド、イベントなど)。たとえば、int型の定義を確認すると、次の構造体とメンバーが見つかります。
namespace System
{
[ComVisible(true)]
public struct Int32 : IComparable, IFormattable, IConvertible, IComparable<Int32>, IEquatable<Int32>
{
public const Int32 MaxValue = 2147483647;
public const Int32 MinValue = -2147483648;
public static Int32 Parse(string s, NumberStyles style, IFormatProvider provider);
...
}
}
メモリ管理 オペレーティングシステムで複数のプロセスが実行されていて、RAMの容量がそれをすべて保持するのに十分ではない場合、オペレーティングシステムはハードディスクの一部をRAMにマップし、ハードディスクへのデータの保存を開始します。オペレーティングシステムは、仮想アドレスが対応する物理アドレスにマップされている特定のテーブルよりも要求を実行するために使用します。メモリを管理するこの機能は、仮想メモリと呼ばれます。
各プロセスでは、使用可能な仮想メモリは次の6つのセクションにまとめられていますが、このトピックの関連性のために、スタックとヒープのみに焦点を当てます。
スタック スタックはLIFO(後入れ先出し)データ構造であり、サイズはオペレーティングシステムに依存します(デフォルトでは、ARM、x86、x64マシンの場合、Windowsは1MBを予約し、Linuxは2MBから8MBに応じて予約します。バージョン)。
メモリのこのセクションは、CPUによって自動的に管理されます。関数が新しい変数を宣言するたびに、コンパイラはスタックにそのサイズと同じ大きさの新しいメモリブロックを割り当て、関数が終了すると、変数のメモリブロックの割り当てが解除されます。
ヒープ このメモリ領域はCPUによって自動的に管理されず、そのサイズはスタックよりも大きくなります。新しいキーワードが呼び出されると、コンパイラは、要求のサイズに適合する最初の空きメモリブロックの検索を開始します。それが見つかると、組み込みのC関数malloc()を使用して予約済みとしてマークされ、その場所へのポインターが返されます。組み込みのC関数free()を使用して、メモリブロックの割り当てを解除することもできます。このメカニズムはメモリの断片化を引き起こし、ポインタを使用してメモリの正しいブロックにアクセスする必要があります。これは、スタックが読み取り/書き込み操作を実行するよりも低速です。
カスタム型と組み込み型 C#には、整数、ブール値、テキスト文字などを表す組み込み型の標準セットが用意されていますが、構造体、クラス、インターフェイス、列挙型などの構造を使用して、独自の型を作成できます。
構造体を使用したカスタムタイプの例は次のとおりです。
struct Point
{
public int X;
public int Y;
};
値と参照の種類 C#の種類は、次のカテゴリに分類できます。
値の型 値の型はSystem.ValueTypeクラスから派生し、この型の変数には、スタック内のメモリ割り当て内の値が含まれます。値タイプの2つのカテゴリーは、構造体と列挙型です。
次の例は、ブール型のメンバーを示しています。ご覧のとおり、System.ValueTypeクラスへの明示的な参照はありません。これは、このクラスが構造体によって継承されているために発生します。
namespace System
{
[ComVisible(true)]
public struct Boolean : IComparable, IConvertible, IComparable<Boolean>, IEquatable<Boolean>
{
public static readonly string TrueString;
public static readonly string FalseString;
public static Boolean Parse(string value);
...
}
}
参照型 一方、参照型には、変数に格納されている実際のデータは含まれていませんが、値が格納されているヒープのメモリアドレスが含まれています。参照型のカテゴリは、クラス、デリゲート、配列、およびインターフェイスです。
実行時に参照型変数が宣言されると、newキーワードを使用して作成されたオブジェクトがそれに割り当てられるまで、値nullが含まれます。
次の例は、ジェネリック型Listのメンバーを示しています。
namespace System.Collections.Generic
{
[DebuggerDisplay("Count = {Count}")]
[DebuggerTypeProxy(typeof(Generic.Mscorlib_CollectionDebugView<>))]
[DefaultMember("Item")]
public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>, IEnumerable, IList, ICollection, IReadOnlyList<T>, IReadOnlyCollection<T>
{
...
public T this[int index] { get; set; }
public int Count { get; }
public int Capacity { get; set; }
public void Add(T item);
public void AddRange(IEnumerable<T> collection);
...
}
}
特定のオブジェクトのメモリアドレスを知りたい場合は、System.Runtime.InteropServicesクラスを使用すると、アンマネージメモリからマネージオブジェクトにアクセスできます。次の例では、静的メソッドGCHandle.Alloc()を使用してハンドルを文字列に割り当て、次にメソッドAddrOfPinnedObjectを使用してそのアドレスを取得します。
string s1 = "Hello World";
GCHandle gch = GCHandle.Alloc(s1, GCHandleType.Pinned);
IntPtr pObj = gch.AddrOfPinnedObject();
Console.WriteLine($"Memory address:{pObj.ToString()}");
出力は
Memory address:39723832
参考文献 公式ドキュメント:https://docs.microsoft.com/en-us/cpp/build/reference/stack-stack-allocations?view=vs-2019
値の型と参照の型の違いについては、規格で明確に述べられている細かい点が数多くあり、特に初心者には理解しにくいものもあります。
ECMA標準33、Common Language Infrastructure(CLI)を参照してください。CLIもISOによって標準化されています。リファレンスを提供しますが、ECMAの場合はPDFをダウンロードする必要があり、そのリンクはバージョン番号によって異なります。ISO規格には費用がかかります。
1つの違いは、値の型はボックス化できますが、参照型は通常はボックス化できないことです。例外はありますが、かなり技術的なものです。
値型は、パラメータのないインスタンスコンストラクタやファイナライザを持つことはできず、それら自体を参照することもできません。自分自身を参照すると、値型が存在する場合に、例えば手段ノードその後のメンバーノードはできませんノード。仕様には他の要件/制限があると思いますが、そうであれば、それらは1つの場所にまとめられません。