Silverlightでアクションの代わりにFuncタイプを使用するコードの簡略化

アクションの代わりにFuncタイプを使用してこのコードをリファクタリングすることは可能ですか? 私が望むのは、アクションコールバックで値を返すために使用された余分なクラス(CalcParams)を取り除くことです。

編集: *注:ダウンロード方法またはFuncは公開する必要があります。

すべてのあなたの助けは非常に高く評価されます。

public class CalcParams
{
    public int CallID;
    public int Result;
    public Action CallbackDone;
    public CalcParams(int callid, Action callback)
    {
        CallID = callid;
        CallbackDone = callback;
    }
}
public partial class MainPage : UserControl
{
    int rand;
    public MainPage()
    {
        InitializeComponent();
        rand = 0;
    }
    public void DownloadDataInBackground(CalcParams calcparams)
    {
        WebClient client = new WebClient();
        Uri uri = new Uri(string.Format("https://www.google.com/search?q={0}", calcparams.CallID));
        client.DownloadStringCompleted += (s, e) =>
        {
            CalcParams localparams = (CalcParams)e.UserState;
            localparams.CallbackDone(localparams.CallID, e.Result.Length);
        };
        client.DownloadStringAsync(uri, calcparams);
    }

    private void button1_Click(object sender, RoutedEventArgs e)
    {
        int callid = rand++;
        Debug.WriteLine("Executing CallID #{0}", callid);
        DownloadDataInBackground(new CalcParams(callid, (c, r) =>
        {
            Debug.WriteLine("The result for the callid {0} is {1}", c, r);
        }));
        callid = 0;
    }
}
1

4 答え

追加のクラスを追加したり、 userState を使用したりすることは、キャプチャの使用によって非常にうまく回避できます。

public partial class MainPage : UserControl
{
    Random rand;

    public MainPage()
    {
        InitializeComponent();
        rand = new Random();
    }

    public void DownloadDataInBackground(int callId, Action returnResult)
    {
        WebClient client = new WebClient();
        Uri uri = new Uri("https://www.google.com/search?q=" + callId.ToString());
        client.DownloadStringCompleted += (s, e) =>
        {
           //Do something else with result ?
            returnResult(e.Result.Length);
        };
        client.DownloadStringAsync(uri);
    }

    private void button1_Click(object sender, RoutedEventArgs e)
    {
        int callid=rand.Next();
        Debug.WriteLine("Executing CallID #{0}", callid);
        DownloadDataInBackground(callid, r =>
        {
            Debug.WriteLine("The result for the callid {0} is {1}", callid, r);
        }));
    }
}

Note that now only an Action is needed, the callid in the click event is "captured" to be used in the returnResult callback.

1
追加された
@montelof:キャプチャがC#でどのように動作するかについてのあなたの理解は少し不具合だと思います。 button_Click の各実行は、 callid のための独自のストレージを取得します。 Internaly callid は実際には、ラムダによって作成されたデリゲートにインスタンスが添付されたクラスのメンバーになります。 button1_Click が呼び出されるたびに新しいインスタンスが作成され、それぞれ callid の "ローカル"値が保持されます。したがって、複数の呼び出しが未処理の要求の値を上書きするという懸念は根拠がありません。
追加された 著者 AnthonyWJones,
@montelof:もしあなたがそれと同じようにbutton_clickを実行している間に変数を変更するなら、それは問題になります。実際のコードでこのような連続的な結合がありますか?そうであれば、非同期コーディングよりも Action よりも Func の方が同期していないようです。 Async APIを使用して作業を行うように指示されている場合は、苦痛を伴うことがあります。この一連の記事をご覧ください: geekswithblogs.net/codingbloke/category/13068.aspx
追加された 著者 AnthonyWJones,
ありがとうございます、この場合は動作しますが、ダウンロードが完了する前にcallidが変更された場合、別の値が表示されます。ダウンロードが完了したときにDownloadDataInBackgroundでキャプチャを使用できますが、button1_Clickは実際にキャプチャしていません。
追加された 著者 montelof,
ありがとうございます、私は説明しようとしていますが、私はラムダを呼び出した後にcallidの値を変更すると、別の値を出力します。コードを試して、button1_Clickの最後の行にcallid = .writelineは0を出力します。callidがクラス内のグローバル変数であった場合も同様です。印刷アクションは間違った値を取得します。これを避けるには、コールバックによってcallidを返すのがなぜですか。 DownloadDataInBackgroundは、完了イベント内で使用されたコールIDを実際にキャプチャしてコールバックを実行しています。
追加された 著者 montelof,
コードの変更を確認してください。問題を示すために乱数を削除しました。コールバックで返されたcallidの代わりに変数callidを表示しようとすると、0が出力されます。私はFuncを使ってコードを単純化することができたと私は元の質問があったかのいずれかの方法を正しい値を印刷し、私は言われていることは不可能です。ありがとう。
追加された 著者 montelof,
私のプロジェクトの冒頭では、キャプチャされた変数を使ってすべてのパラメータ化を最小限にしようとしていましたが、いくつかのストレステストの後、次の呼び出しでいくつかの変数が上書きされたことがわかりました。 、私はほとんどすべてのパラメータを使用することに決めました。それらの記事は非常に興味深いようです、私は病気が将来のプロジェクトでAsyncOperationを使用していると思う、ありがとう。
追加された 著者 montelof,

いいえ、それは叶わない。コールバックはそれだけで、いくつかのパラメータを持つメソッドです。コールバックの戻り値の関数は何ですか(Funcは戻り値を持つアクションです)。典型的なケースでは、paramsクラスで作業しないで、必要な引数を直接渡すのは妥当です。

DownloadDataInBackground(int callId, Action callback);

クラス内のパラメータをラップすると、デザインをメンテナンス可能に保つことができます。いつクラスを使うのか、いつパラメータを使うのかはあなた次第です。

それとは別に、あなたはそれのためにuserstateを使用する必要はなく、単にDownloadDataInBackgroundでcalcparamsをキャプチャすることができます:

1
追加された
下の私の答えを確認してください、私は元の質問より良いまたは最悪のアプローチは、私は別のクラスから実行する必要がありますので、Funcのパブリックを維持することについては、それをあなたが提案する方法でやるが、私はFuncを使ってダウンロードクラスに依存しないようにするために、私のプロジェクトでMEFを使う。その他の理由により。敬具。
追加された 著者 montelof,

あなたのコメントに基づいて、私は自分のコールバックよりもむしろTPLを使うことを提案します。これは人生をはるかに簡単にします:)(今は例外処理をしません)。 ParallelExtensionsExtraのプロジェクトと.NET 4.5には、タスクを返すDownloadStringTaskという別のWebクライアントがあります。あなたがそれを使用していないと仮定すると、簡単に複製することができます。次のコードを確認してください:

    public Task DownloadDataInBackground(int id)
    {
        TaskCompletionSource resultTaskCompletionSource = new TaskCompletionSource(id);

        WebClient client = new WebClient();

        Uri uri = new Uri(string.Format("https://www.google.com/search?q={0}", id));
        client.DownloadStringCompleted += (s, e) =>
        {
            if (e.Error != null)
                resultTaskCompletionSource.SetException(e.Error);
            else if (e.Cancelled)
                resultTaskCompletionSource.SetCanceled();
            else
                resultTaskCompletionSource.SetResult(e.Result);

        };
        client.DownloadStringAsync(uri);

        return resultTaskCompletionSource.Task;
    }


    private void button1_Click(object sender, EventArgs e)
    {
        int callid = rand++;
        Debug.WriteLine("Executing CallID #{0}", callid);

        DownloadDataInBackground(callid).ContinueWith(task =>
        {
            if (task.IsCompleted)
            {
                Debug.WriteLine("Download for {0} completed with a length of {1}", task.AsyncState, task.Result.Length);
            }
        });
    }
1
追加された
ありがとう、これは完璧ですが、私はそれをFuncの中に入れたいと思います。なぜなら、IllがFuncをプロパティとして別のクラスに渡して、そこからダウンロードしたものとは何の関係もないviewmodelを持っているからです。彼らはデカップリングされている、私はMEFを使用して、私はこれが最初の記述の一部ではないことを知っているが、私はFuncを使用したいと述べた部分が悪いです。
追加された 著者 montelof,

これは私の最初のアプローチです。もっと簡単で洗練されたやり方を知りたいのですが。

public partial class MainPage : UserControl
{
    int rand;
    public MainPage()
    {
        InitializeComponent();
        rand = 0;
    }

    public Func, Action> DownloadDataInBackground = (callback) =>
    {
        return (c) =>
        {
            WebClient client = new WebClient();
            Uri uri = new Uri(string.Format("https://www.google.com/search?q={0}", c));
            client.DownloadStringCompleted += (s, e2) =>
            {
                callback(c, e2.Result.Length);
            };
            client.DownloadStringAsync(uri);
        };
    };

    private void button1_Click(object sender, RoutedEventArgs e)
    {
        int callid = rand++;
        Debug.WriteLine("Executing CallID #{0}", callid);
        DownloadDataInBackground((c3, r3) =>Debug.WriteLine("The result for the callid {0} is {1}", c3, r3))(callid);
    }
}
0
追加された