9798


各API用のデータクラスが複数あり、  
各クラスのデータをAPI送受信のManagerクラスに渡したいのですが、  
どのようにすれば良いのかがわかりません。

下記のように仮のデータクラスとしてTestAとTestBを作り、  
APIManager内にどちらのインスタンスが来てもセットできる変数を作成したいです。

●testAクラス

[code]

    using UnityEngine;
    using System.Collections;
    
    public class TestA{
    
        int val1;
        int val2;
        int val3;
    }
    
[/code]

●testBクラス

[code]

    using UnityEngine;
    using System.Collections;
    
    public class TestB  {
    
        int val1;
        long val2;
        string valstr;
    }
    
[/code]

●ApiManagerクラス(シングルトン)

[code]

    using UnityEngine;
    using System;
    using System.Collections;
    using System.Collections.Generic;
    
    public sealed class ApiManager
    {
    
        -----------------追記-----------------
        private APICommunicationThread ThreadOne = new APICommunicationThread();
    
        //　●●●●●●●●●●ここに不定なクラスのインスタンスを持ちたいです。
    
        private static ApiManager m_Instance = new ApiManager();
    
    
        -----------------追記-----------------
        /// <summary>
        /// スリープFLGを解除してスレッド再起動
        /// </summary>
        public void OnClickGoMessagePack()
        {
            ThreadOne.SleepFlgFalse();
        }
    
        private ApiManager()
        {
            //        Console.WriteLine("Created instance.");
        }
    
    
        public static ApiManager Instance
        {
            get
            {
                return m_Instance;
            }
        }
    
        public void DoSomething()
        {
            //        Console.WriteLine("DoSomething is called");
        }
    }
    
[/code]

-----------------追記-----------------

●最終的な処理の流れ  
①各機能でデータ(TestAやTestBなどAPIにより異なる)を作成してもらい、  
ApiManagerの型不定のインスタンスにデータをセット、  
APIのURL、コールバック先のメソッドもセットしてもらい  
ApiManagerの通信スタート（スレッド再開）を呼ぶ。  
②ApiManagerでは通信用クラスに型不定のインスタンス、APIのURL、コールバック先を渡し  
通信用クラスのスレッドを再開させる。  
③通信用クラス内では型不定のインスタンスの型でシリアライザーを作成し、  
シリアライザーのPackメソッドで型不定インスタンスをストリームにパックする  
④指定URLにPackデータを送信  
⑤レスポンス取得後、中身のデータをコールバック先に渡す。

[code]

    using System.Collections;
    using System.Collections.Generic;
    using MsgPack;
    using MsgPack.Serialization;
    using System;
    using System.Net;
    using System.IO;
    using System.Text;
    using System.Collections.ObjectModel;
    using System.Threading;
    
    public class APICommunicationThread
    {
    
        private string MsgLog;
        public string SendUrl;
        public 不定な型 SendDataObj;
        public Type SendType;
    
        public Action<Object> CallBackTo;
    
        public Thread _threadFirst;
    
        public AutoResetEvent _threadEvent;
    
        public string TestString;
    
        public bool SleepFLG = false;
        public bool GoSendFLG = false;
    
        public void ThreadStart()
        {
            SleepFLG = true;
            GoSendFLG = false;
            _threadFirst = new Thread(ThreadWorking);
            _threadEvent = new AutoResetEvent(true);
            _threadFirst.Start();
        }
    
    
        /// <summary>
        /// スレッド処理
        /// </summary>
        public void ThreadWorking()
        {
    
            while (true)
            {
                // 現在、MessagePack処理をおこなっているか？
                if(GoSendFLG == true)
                {
                    SleepFLG = false;
                    GoSendFLG = false;
                    // MessagePack処理
                    MessagePackSend();
                }
                else
                {
                    /// スレッド休止用FLGが立っているか？
                    if (SleepFLG == true)
                    {
                        TestString = "Sleep On";
                        // スレッドを給仕状態にする
                        _threadEvent.WaitOne();
                    }
                    else
                    {
                        TestString = "Sleep Off";
                    }
                }
            }
        }
    
    
    
        /// <summary>
        /// Msgpackを使用してAPI通信
        /// </summary>
        public void MessagePackSend()
        {
            try
            {
    
                MsgLog = "BEF SERIALIZE";
    
                var stream = new MemoryStream();
                // --- シリアライズ
                // Set using Map instead of Array to serialize complex object. See Sample03 for details.
                var context = new SerializationContext();
                context.SerializationMethod = SerializationMethod.Map;
    
                MsgLog = MsgLog + "BEF SERIALIZE2";
                // 1. Create serializer instance.
                var serializer = MessagePackSerializer.Get<不定な型>(context);
    
                MsgLog = MsgLog + "AFT SERIALIZE";
    
                // 2. Serialize object to the specified stream.
                serializer.Pack(stream,不定な型のインスタンス);
    
                MsgLog = MsgLog + "AFT PACK";
    
                byte[] bytes = stream.GetBuffer();
    
                HttpWebRequest req = (HttpWebRequest)WebRequest.Create(SendUrl);
                req.ContentType = "application/x-msgpack; charset=UTF-8";
                req.Method = "POST";
                req.Accept = "application/x-msgpack";
                req.ContentLength = bytes.Length;
    
                MsgLog = MsgLog + "BEF GETREQ";
                // データの設定
                var s = req.GetRequestStream();
                s.Write(bytes, 0, bytes.Length);
                s.Close();
                // ***End your original code***
                MsgLog = MsgLog + "SENT";
    
    
                using (HttpWebResponse wres = (HttpWebResponse)req.GetResponse())
                {
                    using (Stream st = wres.GetResponseStream())
                    {
                        using (MemoryStream memoryStream = new MemoryStream())
                        {
                            byte[] buffer = new byte[4096];
                            int count = 0;
                            do
                            {
                                count = st.Read(buffer, 0, buffer.Length);
                                memoryStream.Write(buffer, 0, count);
    
                            } while (count != 0);
    
                            var deserializer = MessagePackSerializer.Get<不定な型>();
                            // デバッグ用UTF-8 エンコード
                            string unpacktext = System.Text.Encoding.UTF8.GetString(memoryStream.ToArray());
    
                            var unpacked = deserializer.UnpackSingleObject(memoryStream.ToArray());
                            CallBackTo(unpacked);
    
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                MsgLog = ex.Message;
            }
    
            /// 処理が終了したのでスレッド休止用FLGをたてる
            SleepFLG = true;
    
        }
    
        /// <summary>    
        /// スレッドを休止状態から起こす
        /// </summary>
        public void SleepFlgFalse()
        {
            /* スレッド処理を実行するようにフラグを落とします */
            SleepFLG = false;
            GoSendFLG = true;
    
            /* AutoResetEventオブジェクトをシグナル状態にして
                スレッドを再開させます */
            _threadEvent.Set();
        }
    
        public string DisplayLog()
        {
            return MsgLog;
        }
    }
    
[/code]


CC BY-SA 3.0
2015-05-02T14:00:52.137
9777
2015-05-04T07:02:07.110
2015-05-03T09:55:28.587
5261
5261
question
c# unity3d
型が不定のクラスインスタンスを共通クラスに持つ方法がわかりません


## はじめに

user3732298さんのご質問を拝読して、 **データクラスをマネージャ側で操作しない**
と理解しました。また、データの送受信を行うとのことですが、その送受信の手法に関して様々なアプローチがあるため、その点は余談として軽くふれるにとどめます。  
ただし、送受信をどのように設計するかは、どの程度まで不定にすることができるのか、逆に言えばどの程度共通化する必要があるのかと言う点に関わりますので、重要な因子であると思います。

## データクラスの構造が共通の場合

参考となっている、TestA及び、TestBは共に`int
val1;`を持っており、コレが他のデータクラスにも当てはまるような場合は、インターフェース又は抽象クラスを使えば良いかと思います。  
尚、インターフェースを利用するか抽象クラスを利用するかはケースバイケースなので両方を併記して、ここでは深く論じることはさけます。

### インターフェスを利用する場合

[code]

        public interface IDataEnvelope
    {
        int Val1 { get; set; }
    }
    
    public class TestA : IDataEnvelope
    {
        public int Val1 { get; set; }
        public int Val2 { get; set; }
        public int Val3 { get; set; }
    
    
    }
    
    
    public class TestB : IDataEnvelope
    {
        public int Val1 { get; set; }
        public long Val2 { get; set; }
        public string ValStr { get; set; }
    }
    
    public sealed class ApiManager
    {
    
    
    
        private static readonly ApiManager _instance = new ApiManager();
    
        //コレが不定なクラスのフィールドになります。
        private IDataEnvelope _dataEnvelope;
    
        private ApiManager()
        {
            //        Console.WriteLine("Created instance.");
        }
    
    
        public static ApiManager Instance
        {
            get
            {
                return _instance;
            }
        }
    
        public void DoSomething()
        {
            //        Console.WriteLine("DoSomething is called");
        }
    
    
    }
    
[/code]

尚、インターフェースを利用する際は、以下の制限が存在します。

  * メソッド及び、プロパティの定義のみ可能
  * 実装を含めることはできない

### 抽象クラスを利用する場合

抽象クラスを利用する場合は、以下の通りとなります。

[code]

        public abstract class DataEnvelope
    {
        int Val1 { get; set; }
    }
    
    public class TestA : DataEnvelope
    {
        public int Val1 { get; set; }
        public int Val2 { get; set; }
        public int Val3 { get; set; }
    
    
    }
    
    
    public class TestB : DataEnvelope
    {
        public int Val1 { get; set; }
        public long Val2 { get; set; }
        public string ValStr { get; set; }
    }
    
    public sealed class ApiManager
    {
    
    
    
        private static readonly ApiManager _instance = new ApiManager();
    
        //コレが不定なクラスのフィールドになります。
        private DataEnvelope _dataEnvelope;
    
        private ApiManager()
        {
            //        Console.WriteLine("Created instance.");
        }
    
    
        public static ApiManager Instance
        {
            get
            {
                return _instance;
            }
        }
    
        public void DoSomething()
        {
            //        Console.WriteLine("DoSomething is called");
        }
    
    
    }
    
[/code]

抽象クラスを利用する場合は、以下の制限が存在します。

  * 他のクラスから継承ができなくなる
  * 明示的実装ができなくなる

尚、抽象クラスを使うのがこのような場合では一般的ですが、非sealed＋virtual付きメソッド/プロパティでも実装可能です（但し、基底となるクラスがインスタンス可能となりますから、その意味はしっかり理解しなければならないと思います）

## 本当に不定の場合

本当に不定となる場合は、以下のような形になります。。。がデータの送受信を行う場合に共通操作が必要となることが多いと思いますから、いまいち使い道がないかも知れません（[Serializable]属性を使うことでできなくは無いでしょうが・・・）

[code]

        public class TestA
    {
        public int Val1 { get; set; }
        public int Val2 { get; set; }
        public int Val3 { get; set; }
    
    
    }
    
    
    public class TestB
    {
        public int Val1 { get; set; }
        public long Val2 { get; set; }
        public string ValStr { get; set; }
    }
    
    public sealed class ApiManager
    {
    
    
    
        private static readonly ApiManager _instance = new ApiManager();
    
        //コレが不定なクラスのフィールドになります。
        private object _dataEnvelope;
    
        private ApiManager()
        {
            //        Console.WriteLine("Created instance.");
        }
    
    
        public static ApiManager Instance
        {
            get
            {
                return _instance;
            }
        }
    
        public void DoSomething()
        {
            //        Console.WriteLine("DoSomething is called");
        }
    
    
    }
    
[/code]

C#では、いかなるクラス・構造体でもSystem.Objectを必ず継承するので、不定なフィールドをobjectとすることで、文字通り、何でもデータとすることが可能です。

### 余談：どのようにデータを送受信するか

以下は余談ですが、データを送受信する際、シリアライズ・デシリアライズしなければ当然データのやりとりは不可能でしょう。  
その際、データクラスの保持している値のみならず、元々のデータクラスの **型** も重要な情報になります。  
また、どのような方法を使ってデータの送受信をするかは多岐にわたります。  
一般的には、

  * ISerializableインターフェースの利用
  * Serializable属性の利用
  * 自前でバイト配列化
  * 自前で文字列化

等の方法でシリアライズして

  * WCF
  * Remoting
  * 各種ソケットを利用して自前プロトコルスタックの構築
  * ファイルに記録してファイルを送信する

等を使って送信・受信した上で受信後デシリアライズすることになるかと思います。  
今回のご質問と直接の関係はないですが、どのような方法を使ってシリアライズ・デシリアライズするかで先に申し上げたように、設計の根幹にも影響すると思いますので、全体的によく考える必要があると思います。


CC BY-SA 3.0
2015-05-02T14:47:05.987
9779
2015-05-02T14:47:05.987
7287
9777
answer


> var serializer = MessagePackSerializer.Get<不定な型>(context);

この`MessagePackSerializer`とは[これ](http://cli.msgpack.org/doc/html/AllMembers_T_MsgPack_Serialization_MessagePackSerializer.htm)でしょうか？であれば`Get(Type,
SerializationContext)`というメソッドがあるので

[code]

    var serializer = MessagePackSerializer.Get(SendDataObj.GetType(), context);
    
[/code]

と呼び出すことが可能です。`deserializer`の生成も同様に`Get(Type)`を利用できます。その他の「不定な型」には`object`を指定してください。

なお`SendDataObj`が派生型であるとか`null`であるといった理由で別の型を使用したい状況も考えられます。その場合は`Type`型のパラメーターを追加して指定させるといいでしょう。


CC BY-SA 3.0
2015-05-03T12:47:31.030
9796
2015-05-03T13:03:13.973
2015-05-03T13:03:13.973
5750
5750
9777
answer


## はじめに

データの送受信に関する、追加情報をいただけたので、その点に関して回答いたします。  
尚、全開の回答と内容も範囲も大きく異なりますので、別の回答とさせて頂きます。

## 補足

尚、表題に関して、

> 型が不定のクラスインスタンスを共通クラスに持つ方法

ではなく、 **不特定多数の型に対して、統一的な手法でデータの送受信を行う方法**
を求めていると考えましたので、これ以降その仮定に基づいた回答をさせて頂きます。

## お断り

サンプルコードに関しては、

  * 例外のハンドリング
  * 各種ログの生成
  * セキュリティ上の考慮

を全くしておりませんので、ご理解のほどお願いします。  
また、

  * Httpによる通信周りは割愛
  * シングルトンだと都合があまり良くないので、staticメソッド化
  * 非同期に関しては、Threadではなくasnyc/awaitを利用

しております。

[code]

    using System;
    using System.Collections.Generic;
    using System.Threading.Tasks;
    using MsgPack;
    using MsgPack.Serialization;
    
    namespace ConsoleApplication6
    {
    public static class ApiCommunicator
    {
        //鯖から戻ってきたデータをそのまま戻す場合（呼び出し先スレッドでデータを処理する際）
        public static async Task<T> MessagePackSend<T>(Uri serverUrl, T data)
        {
            var serializer = MessagePackSerializer.Get<T>();
    
            //パッキング
            var serializedData = serializer.PackSingleObject(data);
    
            //送受信処理（ここでは遅延挟むだけ）
            var rcv = await SendMessage(serverUrl, serializedData);
    
            //アンパックして戻す
            return serializer.UnpackSingleObject(rcv);
        }
    
        //鯖から戻ってきたデータを指定されたコールバックに通す（戻ってきたデータを非同期的に処理する場合）
        public static async Task MessagePackSend<T>(Uri serverUrl, T data, Action<T> callBack)
        {
            //とりあえずデータを送って貰ってくる。
            var rcvData = await MessagePackSend(serverUrl, data);
    
            await Task.Run(() => callBack(rcvData));
        }
    
        //通信するスタブメソッド
        private static async Task<byte[]> SendMessage(Uri severUrl, byte[] data)
        {
            await Task.Delay(TimeSpan.FromSeconds(1));
    
    
    
            return data;
        }
    }
    
    public class TestA
    {
        public int Val1 { get; set; }
        public int Val2 { get; set; }
        public int Val3 { get; set; }
    }
    
    public class TestB
    {
        public int Val1 { get; set; }
        public long Val2 { get; set; }
        public string ValStr { get; set; }
    }
    
    
    public static class MainEntry
    {
        public static void Main()
        {
            var sampleA = new TestA {Val1 = 42, Val2 = 84, Val3 = 127};
            var sampleB = new TestB {Val1 = 42, Val2 = 65536, ValStr = "hello world"};
    
            var server = new Uri("http://127.0.0.1");
    
            //データのみ受け取る場合
            var taskA = ApiCommunicator.MessagePackSend(server, sampleA);
            taskA.Wait();
            Console.WriteLine("resultA.Value1:" + taskA.Result.Val1);
    
            var taskB = ApiCommunicator.MessagePackSend(server, sampleB);
            taskB.Wait();
            Console.WriteLine("resultB.Value:" + taskB.Result.Val1);
    
            //コールバックする場合
            var task = ApiCommunicator.MessagePackSend(server, sampleA, x => Console.WriteLine("Val2:" + x.Val2));
            task.Wait();
    
            task = ApiCommunicator.MessagePackSend(server, sampleB, x => Console.WriteLine("ValStr:" + x.ValStr));
            task.Wait();
        }
    }
    }
    
[/code]

処理の流れを拝見するに、シングルトンにする必要性と、あえてコールバックアクションとデータを保持する必要が無いかな？と思いましたので、このような形にしております。  
もしコールバックアクションや、データを保持する必要があるのなら、ジェネリッククラスの利用を考えてみてはいかがでしょうか？


CC BY-SA 3.0
2015-05-03T14:41:30.703
9798
2015-05-04T07:02:07.110
2015-05-04T07:02:07.110
7287
7287
9777
answer