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”,不是引用。

您实际上是通过引用传递该值,但随后将其复制到成员变量。“参考”在您的构造函数结束时消失了。

要四处走动,您必须保留实际的源对象,并通过接口引入强依赖或弱依赖,或者以惰性/功能方式 - 通过委托

Ex#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( , ..) 和 structs ( struct MyThing) 总是按值传递,因此您的原件_bytes = bytes制作了 int 的副本)。因此,稍后,我可以查询

Ex#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

Ex#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
}

带有界面的示例很漂亮,但是界面“很小”。一个是可以的,但有时你需要引入几十个这样的单一属性或单一方法接口,它会变得有些无聊和混乱。特别是如果所有这些都是“内部实现”,无论如何都没有发布给任何其他人使用。你可以很容易地用一个短的 lambda 删除这样的小接口,如第三个示例中所示。我没有接收和存储实现接口的对象,而是将参数更改为 Func。这样我需要得到“一个返回 INT 的方法”。他们,我通过了一些方法。请注意,在 期间new SpeedCalculator,我不调用this.getbytes(),我传递不带括号的方法 - 这会导致方法被包装到 Func 委托中,bts(),并将返回当前计数器。这getbytes很少使用,仅在一个地方使用 - 所以我什至可以完全放弃它并在构造函数调用时编写匿名函数,正如您在“甚至”部分中看到的那样。

但是,我建议您现在坚持使用接口,它们更易于阅读和理解。

于 2012-12-26T21:58:53.343 回答