我正在编写一个类来计算定义的一段时间内的平均下载速度,并采用一定数量的样本。我认为这样做的方法是这个类运行一个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 {...}
}
答案 0 :(得分:0)
这是在C#中理解'ref'参数的常见“问题”。你可以看到,与C +不同,C#中有没有实际值引用。
在C ++中,当你通过引用传递时,实际上你会在内部将指针传递给变量。因此,您可以拥有类型为“int&amp;”的类成员变量这是对存储在别处的整数的实际引用。
在C#中,'ref'或'out'参数以类似的方式工作,但没有人谈论指针。您无法存储参考。你不能拥有'ref'级成员。看看你的类:sotrage变量的类型为'int',plain'int',而不是引用。
您实际上是通过-ref传递该值,但随后将其复制到成员变量。 “引用”在构造函数结束时消失了。
要遍历它,你必须保留实际的源对象,并通过接口引入强依赖,或者引入弱的依赖,或者通过委托来执行懒惰/功能方式
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
)都是通过引用或隐式指针传递的,因此通过参数获取FTPDownloadFile并将其分配给成员变量不会复制它,它实际上是由ref传递的(在另一方面,值(int,decimal,..)和结构(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
很少使用,只在这一个地方 - 所以我甚至可以完全删除它并在构造函数调用时编写匿名函数,正如你在“甚至”部分中看到的那样。
但是,我建议你现在坚持使用界面,阅读和理解它们会更清晰。