0

私は現在、定義された数のサンプルを取得して、定義された期間の平均ダウンロード速度を計算するクラスを作成しています。これが機能すると私が思ったのは、このクラスがTimerオブジェクトを実行し、ダウンロードされたバイト(親クラスであるFTPDownloadFileに保持されている)を調べるクラス内のメソッドを呼び出し、そのサンプルをキューに格納することです。しかし、私の問題はダウンロードされたバイト数にアクセスすることです。

その情報にアクセスする私の方法は、ダウンロード計算クラスが構築されたときに渡された参照を介して行われましたが、参照を正しく理解/使用していないようです。元の変数が変化しているのがわかりますが、渡される変数は常に0のように見えます。

誰かが私が間違っていることを教えてもらえますか/私がやりたいことを達成するためのより良い方法を提案できますか?

まず、ダウンロード速度の計算を処理するクラスは次のとおりです。

public class SpeedCalculator
    {
        private const int samples = 5;
        private const int sampleRate = 1000; //In milliseconds
        private int bytesDownloadedSinceLastQuery;
        private System.Threading.Timer queryTimer;
        private Queue<int> byteDeltas = new Queue<int>(samples);
        private int _bytesDownloaded;

        public SpeedCalculator(ref int bytesDownloaded)
        {
            _bytesDownloaded = bytesDownloaded;
        }

        public void StartPolling()
        {
            queryTimer = new System.Threading.Timer(this.QueryByteDelta, null, 0, sampleRate);
        }

        private void QueryByteDelta(object data)
        {
            if (byteDeltas.Count == samples)
            {
                byteDeltas.Dequeue();
            }

            byteDeltas.Enqueue(_bytesDownloaded - bytesDownloadedSinceLastQuery);
            bytesDownloadedSinceLastQuery = _bytesDownloaded;
        }

        /// <summary>
        /// Calculates the average download speed over a predefined sample size.
        /// </summary>
        /// <returns>The average speed in bytes per second.</returns>
        public float GetDownloadSpeed()
        {
            float speed;
            try
            {
                speed = (float)byteDeltas.Average() / ((float)sampleRate / 1000f);
            }
            catch {speed = 0f;}

            return speed;
        }

そのクラスは私のFTPDownloadFileクラスの中に含まれています:

class FTPDownloadFile : IDisposable
{
    private const int recvBufferSize = 2048;
    public int bytesDownloaded;
    public SpeedCalculator Speed;
    private FileStream localFileStream;
    FtpWebResponse ftpResponse;
    Stream ftpStream;
    FtpWebRequest ftpRequest;
    public List<string> log = new List<string>();
    private FileInfo destFile;

    public event EventHandler ConnectionEstablished;

    public FTPDownloadFile()
    {
        bytesDownloaded = 0;
        Speed = new SpeedCalculator(ref bytesDownloaded);
    }

    public void GetFile(string host, string remoteFile, string user, string pass, string localFile)
    {
        //Some code to start the download...
        Speed.StartPolling();
    }

    public class SpeedCalculator {...}
}
4

1 に答える 1

0

これは、C#の「ref」パラメータを理解する上での一般的な「問題」です。ご覧のとおり、C +とは異なり、C#には実際の値の参照はありません

C ++では、参照渡しを行う場合、実際には内部で変数へのポインターを渡します。したがって、他の場所に格納されている整数への実際の参照であるタイプ「int&」のクラスメンバー変数を持つことができます。

C#では、「ref」または「out」パラメーターは同様に機能しますが、ポインターについては誰も話しません。参照を保存することはできません。'ref'クラスメンバーを持つことはできません。クラスを見てください。sotrage変数はタイプ'int'、プレーン'int'であり、参照ではありません。

実際には、その値を-refで渡しますが、それをメンバー変数にコピーします。'参照'は、コンストラクターが終了する時点でなくなります。

それを歩き回るには、実際のソースオブジェクトを保持し、インターフェイスによって強い依存関係を導入するか、インターフェイスによって弱い依存関係を導入するか、デリゲートによって怠惰な/機能的な方法で実行する必要があります

例1:強力なリファレンス

public class SpeedCalculator
{
    private const int samples = 5;
    private const int sampleRate = 1000; //In milliseconds
    private int bytesDownloadedSinceLastQuery;
    private System.Threading.Timer queryTimer;
    private Queue<int> byteDeltas = new Queue<int>(samples);

    private FTPDownloadFile downloader; // CHANGE

    public SpeedCalculator(FTPDownloadFile fileDownloader) // CHANGE
    {
        downloader = fileDownloader;
    }

    public void StartPolling()
    {
        queryTimer = new System.Threading.Timer(this.QueryByteDelta, null, 0, sampleRate);
    }

    private void QueryByteDelta(object data)
    {
        if (byteDeltas.Count == samples)
        {
            byteDeltas.Dequeue();
        }

        byteDeltas.Enqueue(_bytesDownloaded - bytesDownloadedSinceLastQuery);

        bytesDownloadedSinceLastQuery = downloader.bytesDownloaded; // CHANGE
    }

//and in the other file

public FTPDownloadFile()
{
    bytesDownloaded = 0;
    Speed = new SpeedCalculator( this ); // CHANGE
}

C#では、すべてのオブジェクト(class MyObject)は参照または暗黙のポインターによって渡されるため、FTPDownloadFileをパラメーターで受け取り、それをメンバー変数に割り当てると、実際にはrefによって渡されます(一方、値(int、decimal) 、..)とstructs(struct MyThing)は常に値で渡されるため、オリジナル_bytes = bytesはint)のコピーを作成しました。したがって、後で、私はただクエリすることができます

例2:「弱い」参照

public interface IByteCountSource
{
    int BytesDownloaded {get;}
}

public class FTPDownloadFile : IDisposable, IByteCountSource
{
    .....
    public int BytesDownloaded { get { return bytesDownloaded; } }
    .....

    public FTPDownloadFile()
    {
       bytesDownloaded = 0;
       Speed = new SpeedCalculator( this ); // note no change versus Ex#1 !
    }
}

public class SpeedCalculator
{
    ....

    private IByteCountSource bts;

    public SpeedCalculator(IByteCountSource countSource) // no "FTP" information!
    {
        this.bts = countSource;
    }

    ...

    private void QueryByteDelta(object data)
    {
        ....

        bytesDownloadedSinceLastQuery = bts.BytesDownloaded;
    }

最初の例は速くて汚いものでした。一般的に、私たちは通常、クラスが他のすべてについてできるだけ知っていることを望んでいます。では、なぜSpeedCalculatorはFTPDownloadFileについて知っている必要があるのでしょうか。知る必要があるのは、現在のバイト数だけです。そこで、実際のソースを「隠す」ためのインターフェースを導入しました。これで、SpeedCalculatorは、インターフェイスを実装する任意のオブジェクト(FTPDownloadFile、HTTPDownloadFile、またはDummyTestDownloader)から値を取得できます。

例3:デリゲート、無名関数など

public class SpeedCalculator
{
    ....

    private Func<int> bts;

    public SpeedCalculator(Func<int> countSource)
    {
        this.bts = countSource;
    }

    ...

    private void QueryByteDelta(object data)
    {
        ....

        bytesDownloadedSinceLastQuery = bts();
    }

// and in the other file

private int getbytes() { return bytesDownloaded; }

public FTPDownloadFile()
{
    bytesDownloaded = 0;
    Speed = new SpeedCalculator( this.getbytes ); // note it is NOT a getbytes() !
}

// or even

public FTPDownloadFile()
{
    bytesDownloaded = 0;
    Speed = new SpeedCalculator( () => this.bytesDownloaded ); // CHANGE
}

インターフェイスの例はきれいですが、インターフェイスは「小さい」でした。1つは問題ありませんが、そのような1つのプロパティまたは1つのメソッドのインターフェイスを何十も導入する必要がある場合があります。これは、やや退屈で雑然とします。特に、それがすべて「内部実装」であり、他の人が使用できるように公開されていない場合は特にそうです。3番目の例のように、短いラムダを使用してこのような小さなインターフェイスを非常に簡単に削除できます。インターフェースを実装するオブジェクトを受け取って保存する代わりに、パラメーターをFuncに変更しました。このようにして、「INTを返すメソッド」を取得する必要があります。それら、私はいくつかのメソッドを渡します。new SpeedCalculatorの間に、私は呼び出しないことに注意してくださいthis.getbytes()。括弧なしでメソッドを渡します。これにより、メソッドがFuncデリゲートにラップされます。bts()、および現在のカウンターを返します。これgetbytesはめったに使用されず、この1つの場所でのみ使用されます。したがって、「または」の部分でわかるように、コンストラクター呼び出しの時点で完全に削除して無名関数を作成することもできます。

ただし、今のところインターフェイスを使い続けることをお勧めします。インターフェイスは読みやすく、理解しやすいものです。

于 2012-12-26T21:58:53.343 に答える