C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

C# での参照渡しの省略について

[トピック内 9 記事 (1 - 9 表示)]  << 0 >>

■103220 / inTopicNo.1)  C# での参照渡しの省略について
  
□投稿者/ Nak (1回)-(2024/07/03(Wed) 14:26:27)

分類:[C#] 

VB.Net から C# へのコンバート作業を行っています。VB.Net についてはそれなりの開発経験がありますが、C# はまだ初心者段階です。

VB.Net では "Optional ByRef" を使うことで参照渡しの引数を省略することができますが、C# では ref または out パラメーターがある場合は規定値を指定できないというエラーメッセージが表示されます。C# では値渡しの引数しか省略できないのでしょうか。省略できない場合、何か代替となる方法はあるでしょうか。

現在扱っている VB.Net のコードに "Optional ByRef" が多用されており、どうしたものかと考えています。

初歩的な質問で申し訳ありませんが、よろしくお願いします。
引用返信 編集キー/
■103221 / inTopicNo.2)  Re[1]: C# での参照渡しの省略について
□投稿者/ kiku (429回)-(2024/07/03(Wed) 17:18:20)
No103220 (Nak さん) に返信
> VB.Net から C# へのコンバート作業を行っています。VB.Net についてはそれなりの開発経験がありますが、C# はまだ初心者段階です。
> 
> VB.Net では "Optional ByRef" を使うことで参照渡しの引数を省略することができますが、C# では ref または out パラメーターがある場合は規定値を指定できないというエラーメッセージが表示されます。C# では値渡しの引数しか省略できないのでしょうか。省略できない場合、何か代替となる方法はあるでしょうか。
> 
> 現在扱っている VB.Net のコードに "Optional ByRef" が多用されており、どうしたものかと考えています。
> 
> 初歩的な質問で申し訳ありませんが、よろしくお願いします。

VB.NET側の仕様を分かっていない状態で回答してみます。
やりたいことと違っていたらごめんなさい。
下記でどうでしょう。

        private void button1_Click(object sender, EventArgs e)
        {
            int val1 = 0;
            int val2 = 0;
            DoSomething01(val1, ref val2);
            Console.WriteLine(val1); //結果0
            Console.WriteLine(val2); //結果1

            val1 = 0;
            val2 = 0;
            DoSomething03(val1);
            Console.WriteLine(val1); //結果0

            val1 = 0;
            val2 = 0;
            DoSomething03(val1, ref val2);
            Console.WriteLine(val1); //結果0
            Console.WriteLine(val2); //結果1
        }

        public void DoSomething01(int arg1,ref int arg2)
        {
            arg1++;
            arg2++;
        }

        //CS1741	ref パラメーターまたは out パラメーターには既定値を指定できません
        //public void DoSomething02(int arg1, ref int arg2 = 3)
        //{
        //    arg1++;
        //    arg2++;
        //}

        public void DoSomething03(int arg1, int val2 = 3)
        {
            arg1++;
            Console.WriteLine(val2); //結果3
        }

        public void DoSomething03(int arg1, ref int arg2)
        {
            arg1++;
            arg2++;
        }

引用返信 編集キー/
■103222 / inTopicNo.3)  Re[1]: C# での参照渡しの省略について
□投稿者/ WebSurfer (2907回)-(2024/07/04(Thu) 10:22:38)
No103220 (Nak さん) に返信

やりたいことは何なのか、具体例を示したサンプルコードを提示できませんか?

> C# では値渡しの引数しか省略できないのでしょうか。

デフォルト値をメソッドの引数に設定して、メソッドを呼び出すとき、引数を省略した場合
はメソッド内でデフォルト値を使うようにしたいということですよね。

で、ref とか out ではデフォルト値の設定ができないので困っているということですよね。

であれば、in パラメーター修飾子を使ってはいかがですか?(C# 7.2 以降でないと使えな
いそうなので注意)

ただし一方通行です。詳しくは以下の記事を見てください。

in/out/refパラメーター修飾子の違いとは?[C#]
https://atmarkit.itmedia.co.jp/ait/articles/1804/25/news021.html

in パラメーター修飾子
https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/keywords/method-parameters#in-parameter-modifier


> 省略できない場合、何か代替となる方法はあるでしょうか。

ref とか out では文法上省略はできないようです。ref とか out を使う場合の代替としては
引数なしのメソッドのオーバーロードを定義するという案があります。だだし、その場合も、
当たり前ですが一方通行になります。

具体例は以下のサンプルを見てください。

using System;

namespace ConsoleApp8
{
    internal class Program
    {
        static void MethodIn(in int i = 0)
        {
            // i は読み取り専用
            Console.WriteLine($"MethodIn: {i}");
        }

        static void MethodRef(ref int i)
        {
            i = 44;
        }

        static void MethodRef()
        {
            int val = 0;
            MethodRef(ref val);
            Console.WriteLine($"MethodRef: {val}");
        }

        static void Main(string[] args)
        {
            MethodIn();

            MethodIn(33);

            MethodRef();

            int val = 0;
            MethodRef(ref val);
            Console.WriteLine($"MethodRef: {val}");
        }

        // 結果は:
        // MethodIn: 0
        // MethodIn: 33
        // MethodRef: 44
        // MethodRef: 44
    }
}

引用返信 編集キー/
■103226 / inTopicNo.4)  Re[1]: C# での参照渡しの省略について
□投稿者/ 魔界の仮面弁士 (3788回)-(2024/07/04(Thu) 16:59:55)
No103220 (Nak さん) に返信
> 規定値を指定できない
規定値ではなく
既定値ですね。


> VB.Net では "Optional ByRef" を使うことで参照渡しの引数を省略することができますが、
C# の場合、
 Foo(out v); は「実引数 v の値が書き換わることを保証する」
 Foo(ref v); は「実引数 v の値が書き換わるかもしれないし、書き換わらないかもしれない」
という想定です。出力引数であることが重視されており、引数が省略されることは想定されていません。

言語仕様的に直訳はできないので、
 ・オーバーロードで回避する
 ・省略可能引数(Optional)の代わりに、可変個引数(ParamArray⇒params)で代用
 ・参照側の値渡しで代用する(元が immutable な型の場合は、配列などに boxing するなど)
 ・引数を object か dynamic にして、既定値を Type.Missing で処理する
などといった、なんらかの「意訳」が必要になってくるでしょう。
オーバーロードを使った例は他の方が回答されているので省略。


今回は VB→C# へのコンバート作業を行っているとのことですが:
Optional では無かったとしても、参照渡しな引数の呼び出しは、
VB から C# へと翻訳する場合に、追加の「意訳」が必要になるケースがあります。


VB は、ByRef 相当の引数に変数だけでなく、プロパティを渡すこともできます。
プロパティを渡せば、メソッド側でプロパティの値を書き換えることができます。
 Private Sub Foo(ByRef v As Decimal)
  v += 1
 End Sub
 Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
  'プロパティの参照渡し:プロパティが書き換わる
  Foo(NumericUpDown1.Value)

  'ByRef 引数に「式」を渡す:結果的に ByVal 的な動作になり、プロパティは書き換わらない
  Foo((NumericUpDown2.Value))
  Foo(+NumericUpDown3.Value)
 End Sub


ところが C# の場合、ref に渡されるのは「変数」だけです。プロパティは渡せません。
そのため、変数に受け渡さないと、ref/out で結果を受け取れません。
 private void Foo(ref decimal v) => v++;
 private void button1_Click(object sender, EventArgs e)
 {
  // // CS0206: 参照を返さないプロパティまたはインデクサーを out 値または ref 値として使用することはできません
  // Foo(ref numericUpDown1.Value);

  var v = numericUpDown1.Value;
  Foo(ref v);
  numericUpDown1.Value = v;
 }

厳密にいえば「変数」以外にも、「ref 戻り値なメソッドやプロパティ」を渡せるのですが、
いずれにせよ、VB のコードの直訳にはならないことに留意する必要があるでしょう。
引用返信 編集キー/
■103227 / inTopicNo.5)  Re[2]: C# での参照渡しの省略について
□投稿者/ 魔界の仮面弁士 (3789回)-(2024/07/04(Thu) 17:02:56)
No103226 (魔界の仮面弁士) に追記
>  ・省略可能引数(Optional)の代わりに、可変個引数(ParamArray⇒params)で代用

こんなやつ。
融通が利かないので、まるでスマートじゃない。

private void button1_Click(object sender, EventArgs e)
{
  Foo(); // 引数なし

  int a = 10;
  Foo(a); // 引数1個
  Console.WriteLine(a); //10 のまま(書き換わらない)

  int[] b = { 10 };
  Foo(b); // 配列で渡す
  Console.WriteLine(b[0]); //11 になった(書き換わった)

}
private void Foo(params int[] args)
{
  if (args.Length > 0) { args[0]++; } // 既定値は使えないけど、省略されたことは分かる
}
引用返信 編集キー/
■103228 / inTopicNo.6)  Re[2]: C# での参照渡しの省略について
□投稿者/ 魔界の仮面弁士 (3790回)-(2024/07/05(Fri) 10:14:18)
No103226 (魔界の仮面弁士) に追記
>>VB.Net では "Optional ByRef" を使うことで参照渡しの引数を省略することができますが、

試しに、Optional ByRef な DLL を作って利用してみると、

 PowerShell ⇒ [ref] 指定で呼び出すべき出力引数として扱われ、しかも省略可能

 C# ⇒ ref 指定で呼び出すべき出力引数として扱われるが、省略不可

となりました。

C# から見た場合、Optional ByRef な引数は、「省略可能ではない、ただの ref 引数」として
扱われてしまい、既定値の出番がありません。dynamic 指定で呼び出した場合も引数を省略できませんし、
COM のように Missing.Value で処理できるというわけでもないようです。
(リフレクションしてみると、参照渡しパラメーターの既定値を取り出すことはできますが…)


ということで、仮に宣言できたとしても、まともに呼び出せるものではありませんので、

 ・移植元のコードが、ByRef 引数への書き込みが発生しているか確認する
  ⇒出力引数として使われていない場合は ByVal で充分!

 ・Optional ByVal への置換では要件を満たせない場合は、オーバーロードを使って意訳する

といった対応が現実的なところかと思います。
引用返信 編集キー/
■103229 / inTopicNo.7)  Re[3]: C# での参照渡しの省略について
□投稿者/ kiku (430回)-(2024/07/05(Fri) 15:19:51)
No103228 (魔界の仮面弁士 さん) に返信
> ■No103226 (魔界の仮面弁士) に追記

> ということで、仮に宣言できたとしても、まともに呼び出せるものではありませんので、

調査ありがとうございます。
引用返信 編集キー/
■103232 / inTopicNo.8)  Re[3]: C# での参照渡しの省略について
□投稿者/ WebSurfer (2908回)-(2024/07/06(Sat) 10:16:13)
No103228 (魔界の仮面弁士 さん) に返信

> ・移植元のコードが、ByRef 引数への書き込みが発生しているか確認する
>   ⇒出力引数として使われていない場合は ByVal で充分!
> ・Optional ByVal への置換では要件を満たせない場合は、オーバーロードを使って意訳する

上の私の回答で紹介したように、in パラメーター修飾子を使うという手段が
あると思います。

「ByVal で充分!」かどうかは議論の余地があるのでは? 質問者さんの使い
方では充分なのかもしれませんが、それは不明ですし。(裏で通じているとか
で質問者さんの使い方が分かっているのでしょうか?)

私の回答で紹介した Microsoft のドキュメントを見てください。そのドキュ
メントに書いてあるin パラメーター修飾子の利点を抜粋しておきます。

"in パラメーターを使用して定義されたメソッドには、パフォーマンスを最
適化できる可能性があります。 一部の struct 型引数は、サイズが大きくな
る可能性があり、メソッドが短いループまたは重要なコード パスで呼び出さ
れる場合、その構造のコピーのコストが大きくなります。 呼び出されたメソ
ッドは引数の状態を変更しないため、メソッドで in パラメーターを宣言して、
参照渡しで安全に渡すことができる引数を指定します。 参照によりこれらの
引数を渡して、(可能性がある) 高額なコピーを回避します。"

質問者さんの使い方ではそんな配慮は不要ということであれば値渡しで十分
かもしれませんが。
引用返信 編集キー/
■103254 / inTopicNo.9)  Re[4]: C# での参照渡しの省略について
□投稿者/ Nak (3回)-(2024/07/11(Thu) 14:27:13)
皆様、回答いただきありがとうございました。しばらくネット接続できない期間があり、返事が遅くなりました。

現在コンバートを行っている VB.Net で多用されている "Optional ByRef" は、例えばこんな感じです。(実際のコードではなく、サンプルとして作成した「配列の最大値を求めるプロシージャ」の例)

Sub array_max(ByVal n As Long, ByVal array() As Double, ByRef max As Double, Optional ByRef max_no As Long)

n が配列の要素数、array が実数の配列で、max が配列の最大値, max_no が最大値の番号を返します。

配列の最大値のみを求めるときの呼出し:

Call array_max(n, array, max)

配列の最大値と、その最大値が配列の何番目にあるかを求めるときの呼出し:

Call array_max(n, array, max, max_no)

このように目的によって呼び出し方を変えるプロシージャが多用されているため、このまま C# にコンバートすると不具合が発生することになります。

回答を拝見していると、やはり C# では文法上省略できないようですね。今回は引数を省略しないように書き換える(上記の例だと、最大値の番号が必要ない場合でも引数 max_no を渡す)ことで対処したいと考えています。回答いただき、ありがとうございました。
解決済み
引用返信 編集キー/

このトピックをツリーで一括表示


トピック内ページ移動 / << 0 >>

このトピックに書きこむ