none
戻り値の異なるオーバーロード RRS feed

  • 質問

  • お世話になっております。

    技術的な質問か、設計の質問か明確ではないのですが
    表題の通り、戻り値を複数にしたいです。

    要求:
     バイナリファイルを読込み、そのデータ内容を返したい
     (サイズは100byte程度)

    したいこと:
     バイナリファイルのヘッダに【データ種別】があり、その種別により
    データ構造が異なる。
    なので、引数としてはファイルパス、戻り値としてはデータ種別によって
    それぞれの構造体を返したい。
    (データ構造はバイナリと同じ配置)

    ご教示頂きたい点:
     そもそも上述の戻り値を値で返す設計がおかしいのか、技術的に適した
    設計があるのか教えてください。

    2019年5月8日 4:29

回答

  • C# ではできないですね。

    戻り値じゃなくてもいいのであれば、

    bool TryGet( string filePath, out int value ){...}
    bool TryGet( string filePath, out bool value ){...}
    bool TryGet( string filePath, out string value ){...}
    bool TryGet( string filePath, out Hoge value ){...}

    というように、引数として、受け取る型の変数を渡すという方法もあります。

    簡素化のためにプリミティブな型などを載せていますが、任意のデータ型(値型でも参照型でも)で受け渡し可能です。

    受け取りたい型が最初からわかっていてということであればこれが一番かなぁ?と思います。


    とっちゃん@わんくま同盟, Visual Studio and Development Technologies http://blogs.wankuma.com/tocchann/default.aspx

    • 回答としてマーク コーベル 2019年5月8日 6:01
    2019年5月8日 5:13
  • すいません、「構造体」のご希望に沿ってないですが・・・。【データ種別】の構造は、互いに「まったく」違うのでしょうか? 「かなり似ている」のでしょうか?

    小生の頭に浮かんだ方法は「かなり似ている」場合に適用可能だと思うのですが、同じ部分で基本クラスとして定義し、派生させたクラスに異なる部分の定義を入れる・・・。

    そうすれば、

    メソッドの戻り値のタイプは基本クラスに統一することになりますが、

    戻った値のタイプを派生クラスと照合する、その結果で処理を変える、ということは可能になります。


    • 編集済み 外池 2019年5月8日 5:08
    • 回答としてマーク コーベル 2019年5月8日 6:01
    2019年5月8日 5:03
  • すみません、たしかに、オーバーロードできません。
    オーバーライドしてました。
    intとboolでは無理ですが、クラスならベースクラスを作る、インターフェイスを作る、で対応できます。しています。

    Jitta@わんくま同盟

    • 回答としてマーク コーベル 2019年5月8日 6:01
    2019年5月8日 5:04
  • 仮に実現できたとして、呼び出し側は test(0) のようにしか書けません。そうなるとコンパイラーは2つあるオーバーロードのうち、どちらを呼べばいいのか決定できません。
    こういった事情もあり、戻り値のみが異なるオーバーロードは定義できない仕様となっています。

    目的にもよりますが、デシリアライザーがobject型を返すのをまねしてはどうでしょうか?

    • 回答としてマーク コーベル 2019年5月8日 6:01
    2019年5月8日 5:20

すべての返信

  • 戻り値が異なるオーバーロードが宣言できない、と言ってます?
    それとも、他の何かですか?
    私は、同じデータのバージョン違いを返すメソッドを、戻り値をインターフェイスで宣言しています。

    Jitta@わんくま同盟

    2019年5月8日 4:43
  • Jittaさん、ご回答ありがとうございます。

    >戻り値が異なるオーバーロードが宣言できない、と言ってます?

    はい、引数が同一だと出来ないと思っておりますが勘違いでしょうか?

    int test(int i) { return 0; }
    bool test(int i) { return false; }

    戻り値をインターフェースにすると可能なのでしょうか!?
    ありがとうございます、調べてみます。

    2019年5月8日 4:52
  • すいません、「構造体」のご希望に沿ってないですが・・・。【データ種別】の構造は、互いに「まったく」違うのでしょうか? 「かなり似ている」のでしょうか?

    小生の頭に浮かんだ方法は「かなり似ている」場合に適用可能だと思うのですが、同じ部分で基本クラスとして定義し、派生させたクラスに異なる部分の定義を入れる・・・。

    そうすれば、

    メソッドの戻り値のタイプは基本クラスに統一することになりますが、

    戻った値のタイプを派生クラスと照合する、その結果で処理を変える、ということは可能になります。


    • 編集済み 外池 2019年5月8日 5:08
    • 回答としてマーク コーベル 2019年5月8日 6:01
    2019年5月8日 5:03
  • すみません、たしかに、オーバーロードできません。
    オーバーライドしてました。
    intとboolでは無理ですが、クラスならベースクラスを作る、インターフェイスを作る、で対応できます。しています。

    Jitta@わんくま同盟

    • 回答としてマーク コーベル 2019年5月8日 6:01
    2019年5月8日 5:04
  • C# ではできないですね。

    戻り値じゃなくてもいいのであれば、

    bool TryGet( string filePath, out int value ){...}
    bool TryGet( string filePath, out bool value ){...}
    bool TryGet( string filePath, out string value ){...}
    bool TryGet( string filePath, out Hoge value ){...}

    というように、引数として、受け取る型の変数を渡すという方法もあります。

    簡素化のためにプリミティブな型などを載せていますが、任意のデータ型(値型でも参照型でも)で受け渡し可能です。

    受け取りたい型が最初からわかっていてということであればこれが一番かなぁ?と思います。


    とっちゃん@わんくま同盟, Visual Studio and Development Technologies http://blogs.wankuma.com/tocchann/default.aspx

    • 回答としてマーク コーベル 2019年5月8日 6:01
    2019年5月8日 5:13
  • 仮に実現できたとして、呼び出し側は test(0) のようにしか書けません。そうなるとコンパイラーは2つあるオーバーロードのうち、どちらを呼べばいいのか決定できません。
    こういった事情もあり、戻り値のみが異なるオーバーロードは定義できない仕様となっています。

    目的にもよりますが、デシリアライザーがobject型を返すのをまねしてはどうでしょうか?

    • 回答としてマーク コーベル 2019年5月8日 6:01
    2019年5月8日 5:20
  • 皆様、ご回答ありがとうございます。

    様々な解決方法(設計)があるんだなと勉強させて頂いてます。

    インターフェースとデシリアライザー、私の知能では理解に時間がかかりそうです、、

    佐祐理さんの、仰るとおりオーバーロードとしてはおかしくなりますよね。
    本来であれば、ファイルパスのみからデータ種別と適したデータ構造を返したかったのですが
    戻り値にインターフェースという発想がなくオーバーロードを考えました。

    データ構造は、メーカによって内容は違うもののデータ構造(区切り)は統一されているので
    クラスを継承して…と考えましたがデータ楮は保持したまま出力したいのでクラスがデータ構造を
    保持できるかも併せて調査したいと思います。

    結果、私のスキルでは
    Getデータ種別 ⇒ 参照渡し が一番いいかと思いました。

    皆様、本当に勉強させていただきました!

    2019年5月8日 5:58
  • ちょっと気になったので。ウザいウンチクで申し訳ないけど。

    <quote>

    ファイルパスのみからデータ種別と適したデータ構造を返したかった

    </quote>

    Object Oriented は、「オブジェクト指向」という訳があてられていますが、言葉の意味からすると、誤訳です。 Oriented は「指向」(ある方向を向いていること)よりも、「志向」(ある目的に気持ちを向けること)の方が適切です。何に?いささか反復的になりますが、Object には「目的」という意味もあります。なぜこんな話をするかというと、言葉には人の思考を縛る力があるからです。「オブジェクト指向」という言葉によって、「オブジェクトの方を向く→オブジェクトを使えば良い」という思考に縛られているのではないか、と考えます。
    前置きが長くなりましたが、何を目的とするか考え、その目的に沿っているかどうかを考えることこそ、Object Oriented Design ではないかと考えます。この、「何を目的とするか考え、その目的に沿っているかどうかを考える」という視点に立ちます。

    「ファイルパスのみからデータ種別と、適したデータ構造を返したかった」と言うことですが、これを実現するメソッドは、どのような目的を持ったクラスに属するのでしょうか。そのクラスの目的と、複数のデータ構造を返すという目的は、同じ方向を向いているでしょうか。外池さんが言及されていますが、返される情報は、
    「【データ種別】の構造は、互いに「まったく」違うのでしょうか? 「かなり似ている」のでしょうか?」
    返す情報が、全く異なっているなら、全く異なったクラスにメソッドを定義するべきで、そうすると「戻り値だけが異なるオーバーロードが実装できない」という問題は発生しません。あるいは、そのデータ種別自体に、「ファイルから再現する」というメソッドを用意してもいいかもしれません。
    あるいは、全く異なっていても(少しは似ている、かもしれないけど)、「同じ操作をするデータ種別」なら、インターフェイスを定義して、それを返すようにします。

    
    // 指定したファイルに書くことができる
    interface Writable
    {
        void Write(string filename);
    }
    
    class A : Writable
    {
        public A(string filename)
        { }
    
        public void Write(string filename)
        { }
    }
    
    class B : Writable
    {
        public B(string filename)
        { }
    
        public void Write(string filename)
        { }
    }
    
    class Loader
    {
        public static Writable Load(string filename)
        {
            Writable result = null;
            if (filename.StartWith(@"C:\"))
                result = new A();
            else
                result = new B();
            return result;
        }
    }
    
        void Hoge()
        {
            Writable writable = Loader.Load(filename);
            writable.Write(newFilename);
        }
    

    究極的には、すべてのクラスは「何かを表す」という目的を持っているわけで、その「何か」が Object クラスであるわけです。デシリアライザーは、すべての「何か」に共通して「シリアル化を解除する」という目的を果たすので、Object 型を返します。同じように、「すべての何かに共通して、ファイルから読み取る」という目的を果たすメソッドを実装できます。

    
    class C
    {
        public C(string filename)
        { }
    }
    
    class D
    {
        public D(string filename)
        { }
    }
    
    class Loader
    {
        // 条件と型の組のリスト
        static List<Tuple<string, Type>> list = new List<Tuple<string, Type>>
        {
            new Tuple<string, Type>( @"C:\", typeof(C) ),
        };
    
        public static Object Load(string filename)
        {
            // filename が条件から始まる組を探す。
            var y = list.Where(x => filename.StartsWith(x.Item1));
            // 組が1つ以上見つかったら、1つめを適用する。
            // 見つからなかったら D クラス。
            Type target = y.Count > 0 ? y.First().Item2 : typeof(D);
            // 型からコンストラクター情報を取得する。
            var constructor = target.GetConstructor(new Type[] { typeof(string) });
            // コンストラクターを実行する。
            return constructor.Invoke(new object[] { filename });
        }
    }
    
        void Hoge()
        {
            Object obj = Loader.Load(filename);
            if (obj is C objC)
            {
                // objC を使う
            }
            else if (obj is D objD)
            {
                // objD を使う
            }
        }
    

    ただし、呼び出し方には注意が必要です。 out 修飾子を使う時も同じですが、メソッドを呼び出す側で、型の判別をしなければなりません。 XmlSerializer や BinarySerializer は、データの中に型の情報が含まれるので、その型情報からリフレクションを利用してインスタンスを作成します。上の例では、パスからデータ構造が決まると言うことなので、パスと対応する型の組(Tuple)を作りました。

    また、こんなこともできます。

    
    class E
    {
        public E(string filename)
        { }
    
        public bool IsMatch(string filename)
        {
            return filename.StartWith(@"C:\");
        }
    }
    
    class Loader
    {
        public static Object Load(string filename)
        {
            // 実行しているアセンブリに登録されている型をすべて列挙する。
            foreach (Type t in Assembly.GetExecutingAssembly().GetTypes())
            {
                var m = t.GetMethod("IsMatch");
                if (m != null)
                {
                    // t は "IsMatch" メソッドを持っている。
                    if ((bool)m.Invoke(filename))
                    {
                        // 型からコンストラクター情報を取得する。
                        var constructor = t.GetConstructor(new Type[] { typeof(string) });
                        // コンストラクターを実行する。
                        return constructor.Invoke(new object[] { filename });
                    }
                }
            }
    
            return null;
        }
    }
    

    "IsMatch" という名前のメソッドを持っているクラスを探し、そのメソッドが true を返せば、クラスのインスタンスを生成します。このコード例はエラー処理をほぼしていません。


    Jitta@わんくま同盟

    2019年5月9日 13:05