2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【C#】Classの参照を渡すと内部の変数が参照渡しと同様の振る舞いをする件について

Last updated at Posted at 2024-10-22

つたないですがTestCodeを
とても意外な結果でした。
指摘があったのですが、これを参照値渡しと呼び、参照渡しとは区別されるそうです。
まあ普通に便利だったんでいいんですが。

XAML

<Window x:Class="DataClassの参照渡し.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:DataClassの参照渡し"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <StackPanel>
        <Button x:Name="button1" Height="50" Width="100"
        HorizontalAlignment="Left" Content="button1" Click="button1_Click"/>
            <Button x:Name="button2" Height="50" Width="100"
HorizontalAlignment="Left" Content="button2" Click="button2_Click"/>
            <Label x:Name="label1" FontSize="16"/>
                
        </StackPanel>
    </Grid>
</Window>

Code Behaind

using System.Windows;

namespace DataClassの参照渡し
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

        }
        button1DataClass bu1d = null!;
        private void button1_Click(object sender, RoutedEventArgs e)
        {
            //Buttonを押すとisSucessffmpegがtrue

            bu1d = new();
            bu1d.isSucessffmpeg = true;
            label1.Content = $"{nameof(bu1d.isSucessffmpeg)} は $\"{bu1d.isSucessffmpeg}\"";
    //結果
    //sSucessffmpeg ( $"True"
        }

        private void button2_Click(object sender, RoutedEventArgs e)
        {
            //Buttonを押すとbutton1DataClassを参照渡しし、button2DataClassのisSucessffmpegがfalse
            button2DataClass bu2d = new(bu1d);
            bu2d.bu1dinBud1Class.isSucessffmpeg = false;

            label1.Content = $"{nameof(bu1d.isSucessffmpeg)} は $\"{bu1d.isSucessffmpeg}\"";
          //結果  isSucessffmpeg は $"False"
        }


        public class button1DataClass
        {


            public bool isSucessffmpeg;
        }
        public class button2DataClass
        {
            public button2DataClass(button1DataClass bu1d)
            {

                bu1dinBud1Class = bu1d;
                //データクラスを参照渡し
            }



          public  button1DataClass bu1dinBud1Class;
        }

    }
}

普通こうなると思わないじゃないですか?
Falseにならなかったらstaticで渡すつもりだったんで想定外の挙動でした(笑)

ChatGPTが同じことを言うので、ハルシネーションだと思っていました。

更に言うと、普通こんなことしないでメソッドで引数取ると思う(笑) 別に完全コンストラクタじゃないし

ChatGPTによると参照型の基本的な仕様だそうで、C#1.0から変わってないそうです。あんま気づきにくい事だと思う:sweat:

これは参照渡し(ref)ではなく参照値渡しである

という指摘があった。

そこでChatGPTで何が出来て、何が出来ないのか聞いてみた。

参照渡し(ref)でできて、参照値渡し(通常の参照型オブジェクトの渡し方)ではできないことをいくつか具体的に説明します。

1. 呼び出し元の変数自体を変更する

参照渡し(ref)では、呼び出し元の変数そのものを関数やメソッド内で変更することができます。これに対し、参照値渡し(通常の参照型オブジェクトの渡し方)では、変数の中身(オブジェクト自体)のプロパティなどを変更することはできますが、変数そのものを別のオブジェクトに置き換えることはできません

例: 参照渡し (ref) の場合

public class DataClass
{
    public bool isSuccess;
}

public void ModifyObject(ref DataClass obj)
{
    // 新しいオブジェクトを生成して、元のオブジェクトを置き換える
    obj = new DataClass();
    obj.isSuccess = true;
}

DataClass myObj = new DataClass();
ModifyObject(ref myObj);
// myObjは新しいオブジェクトに置き換えられ、isSuccessはtrueに設定される

この例では、refを使っているため、ModifyObjectメソッド内でmyObj変数自体を新しいインスタンスに置き換えることができます。呼び出し元のmyObjは、新しいオブジェクトを指すようになります。

例: 通常の参照値渡しの場合

public void ModifyObject(DataClass obj)
{
    // これは元のオブジェクトに影響を与えない
    obj = new DataClass();
    obj.isSuccess = true;
}

DataClass myObj = new DataClass();
ModifyObject(myObj);
// myObjは元のオブジェクトのままで、isSuccessも変更されない

この場合、objに新しいインスタンスを代入しても、呼び出し元のmyObjは変更されませんobjはローカルな変数なので、関数のスコープが終わるとobjへの変更は消えます。

2. 値型でもオブジェクトのプロパティを変更できる

参照渡し(ref)は、値型(structなど) を渡す場合にも使えます。この場合、関数内で値型のフィールドやプロパティを変更しても、呼び出し元に反映されます。一方、通常の値渡しでは、値型のコピーが渡されるため、関数内での変更は呼び出し元に影響を与えません。

例: 値型の参照渡し

public struct MyStruct
{
    public int Value;
}

public void ModifyStruct(ref MyStruct myStruct)
{
    myStruct.Value = 100;
}

MyStruct myStruct = new MyStruct();
ModifyStruct(ref myStruct);
// 呼び出し元の myStruct.Value が 100 に変更される

refを使うことで、値型であっても元のオブジェクトの値を変更できます。

例: 通常の値渡し

public void ModifyStruct(MyStruct myStruct)
{
    myStruct.Value = 100;
}

MyStruct myStruct = new MyStruct();
ModifyStruct(myStruct);
// myStruct.Value は変更されない

この場合、myStructはコピーされて渡されるため、関数内での変更は呼び出し元に影響を与えません。

3. 複数のoutref引数を使って値を返す

refoutキーワードを使うと、メソッドが複数の値を返すことが可能です。通常の参照値渡しでは、1つのオブジェクトのプロパティなどを変更することはできますが、複数の変数に対して一度に値を返すことはできません。

例: 複数のref引数

public void ModifyValues(ref int a, ref int b)
{
    a += 10;
    b += 20;
}

int x = 5;
int y = 15;
ModifyValues(ref x, ref y);
// x は 15, y は 35 に変更される

この場合、2つのref引数を使って、2つの異なる変数の値を一度に変更することができます。

まとめ

  • 呼び出し元の変数自体を変更できる: refを使うと、呼び出し元の変数自体を新しいオブジェクトで置き換えることが可能。通常の参照値渡しでは、このような動作はできません。
  • 値型に対しても影響を与えられる: refを使えば、値型(structなど)でも呼び出し元に変更を反映させることが可能。
  • 複数の変数に対して一度に値を返す: refoutを使えば、複数の変数に同時に値を返すことができるが、通常の参照値渡しではこれは不可能です。

これらの特徴により、refを使うことで、通常の参照値渡しではできない強力な操作が可能になります。

あとがき

通常の参照値渡しでも十分だと思うけど(今回の場合はアドレスのコピーで十分)、まあrefの方がいろいろ出来るのは分った。

2
3
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?