"不要多次释放对象"的小随笔

【题外话】

一. net的对象使用一般分为三种情况﹕

Windows服务在Visual Studio 以前的版本中叫NT服务,在VS.net启用了新的名称。用Visual C# 创建Windows服务不是一件困难的事,本文就将指导你一步一步创建一个Windows服务并使用它。这个服务在启动和停止时,向一个文本文件中写入一 些文字信息。

  文本文件是一种常用的文件格式,所以如何处理文本文件也就成为编程的一个重点。本文就来探讨一下用C#是如何来处理文本文件。其内容重点就是如何读取文本文件内容、如何改变文本文件的内容,以及如何用C#来实现对读取后的文本文件的打印预览和打印。
  
  一. 本文程序设计和运行的软件环境:
  
  (1).微软公司视窗2000服务器版
  
  (2)..Net FrameWork SDK Beta 2
  
  二. C#处理文本文件的一些重要环节:
  
  (1).如何读取文本文件内容:
  
  在本文介绍的程序中,是把读取的文本文件,用一个richTextBox组件显示出来。要读取文本文件,必须使用到"StreamReader"类,这个类是由名字空间"System.IO"中定义的。通过"StreamReader"类的"ReadLine ( )"方法,就可以读取打开数据流当前行的数据了。下面代码实现的功能就是读取"C:file.txt"并在richTextBox1组件中显示出来:
  
  FileStream fs = new FileStream ( "C:\file.txt"  , FileMode.Open , FileAccess.Read ) ;
  StreamReader m_streamReader = new StreamReader ( fs ) ;
  //使用StreamReader类来读取文件
  m_streamReader.BaseStream.Seek ( 0 , SeekOrigin.Begin ) ;
  // 从数据流中读取每一行,直到文件的最后一行,并在richTextBox1中显示出内容
  this.richTextBox1.Text = "" ;
  string strLine = m_streamReader.ReadLine ( ) ;
  while ( strLine != null )
  {
  this.richTextBox1.Text  = strLine   "n" ;
  strLine = m_streamReader.ReadLine ( ) ;
  }
  //关闭此StreamReader对象
  m_streamReader.Close ( ) ;
  
  (2).如何改变文本文件中数据内容:
  
  在本文介绍的程序中,改变文本文件数据内容的功能是通过改变richTextBox1中的内容来实现的,当richTextBox1这的内容改变后,按动"另存为",就把richTextBox1中内容存储到指定的文本文件中了。要想改变文本文件内容,要使用到"StreamWriter"类,这个类和"StreamReader"一样,都是由"System.IO"名字空间来定义的。通过"StreamWriter"类的"Write ( )"方法,就可以轻松实现文本文件内容的更改了。下面代码的功能是:如果"C"盘存在"file.txt",则把richTextBox1中的内容写入到"file.txt"中,如果不存在,则创建此文件,然后在写入文本数据。
  
  //创建一个文件流,用以写入或者创建一个StreamWriter
  FileStream fs = new FileStream ( "C\file.txt"  , FileMode.OpenOrCreate , FileAccess.Write ) ;
  StreamWriter m_streamWriter = new StreamWriter ( fs ) ;
  m_streamWriter.Flush ( ) ;
  // 使用StreamWriter来往文件中写入内容
  m_streamWriter.BaseStream.Seek ( 0 , SeekOrigin.Begin ) ;
  // 把richTextBox1中的内容写入文件
  m_streamWriter.Write ( richTextBox1.Text ) ;
  //关闭此文件
  m_streamWriter.Flush ( ) ;
  m_streamWriter.Close ( ) ;
  
  从上面这二个代码可以,写入数据比起读取数据要显得容易些。
  
  (3).如何实现打印预览:
  
  打印预览是通过打印预览对话框来实现的,实现对读取得文本文件的打印预览,最为重要的就是要通知打印预览对话框所要预览的文件的内容。下面代码就是把richTextBox1中显示的内容,通过打印预览对话框显示出来:
  
  string strText = richTextBox1.Text ;
  StringReader myReader = new StringReader ( strText ) ;
  PrintPreviewDialog printPreviewDialog1 = new PrintPreviewDialog ( ) ;
  printPreviewDialog1.Document = ThePrintDocument  ;
  printPreviewDialog1.FormBorderStyle = FormBorderStyle.Fixed3D  ;
  printPreviewDialog1.ShowDialog ( ) ;
  
  (4).如何打印文件:
  
  在名字空间"System.Drawing.Printing"中定义了一个类"PrintDocument",通过调用此类的"Print"方法就可以触发在此名字空间中封装的另外一个事件"PrintPage"。在此事件中设定要打印的文档内容,从而实现队文本文件的打印操作。下面代码是调用"PrintDocument"的"Print"方法,和调用事件"PrintPage"来打印richTextBox1中的内容:
  
  ThePrintDocument.Print ( ) ;//其中ThePrintDocument是"PrintDocument"类的一个对象
  
  下列代码是设定打印内容即打印richTextBox1中的内容:
  
  float linesPerPage = 0 ;
  float yPosition = 0 ;
  int count = 0 ;
  float leftMargin = ev.MarginBounds.Left ;
  float topMargin = ev.MarginBounds.Top ;
  string line = null ;
  Font printFont = richTextBox1.Font ;
  SolidBrush myBrush = new SolidBrush ( Color.Black ) ;
  //计算每一页打印多少行
  linesPerPage = ev.MarginBounds.Height / printFont.GetHeight ( ev.Graphics ) ;
  //重复使用StringReader对象 ,打印出richTextBox1中的所有内容
  while ( count < linesPerPage && ( ( line = myReader.ReadLine ( ) ) != null ) )
  {
  // 计算出要打印的下一行所基于页面的位置
  yPosition = topMargin   ( count * printFont.GetHeight ( ev.Graphics ) ) ;
  // 打印出richTextBox1中的下一行内容
  ev.Graphics.DrawString ( line , printFont , myBrush , leftMargin , yPosition , new StringFormat ( ) ) ;
  count  ;
  }
  // 判断如果还要下一页,则继续打印
  if ( line != null )
  ev.HasMorePages = true ;
  else
  ev.HasMorePages = false ;
  myBrush.Dispose ( ) ;
  
  注释:由于在上述的代码中省掉了这些类所对于地名字空间,所以要想成功的编译和运行上述代码,就要在程序头部要导入所使用的名字空间。
  
  三. 用C#处理文本文件的完整源程序代码(control.cs):
  
  掌握了上面这些关键步骤,就可以方便的得到用C#来处理文本文件的一个完整的源程序,具体如下:
  
  using System ;
  using System.Drawing ;
  using System.Collections ;
  using System.ComponentModel ;
  using System.Windows.Forms ;
  using System.Data ;
  using System.IO ;
  using System.Drawing.Printing ;
  public class Form1 : Form
  {
  private RichTextBox richTextBox1 ;
  private Button button1 ;
  private Button button2 ;
  private Button button3 ;
  private Button button4 ;
  private Button button5 ;
  private OpenFileDialog openFileDialog1 ;
  private SaveFileDialog saveFileDialog1 ;
  private PrintDialog printDialog1 ;
  private PrintDocument ThePrintDocument ;
  private PrintPreviewDialog printPreviewDialog1 ;
  private StringReader myReader ;
  private System.ComponentModel.Container components = null ;
  
  public Form1 ( )
  {
  //初始化窗体中的各个组件
  InitializeComponent ( ) ;
  }
  //清除程序中使用多的资源
  protected override void Dispose ( bool disposing )
  {
  if ( disposing )
  {
  if ( components != null )
  {
  components.Dispose ( ) ;
  }
  }
  base.Dispose ( disposing ) ;
  }
  private void InitializeComponent ( )
  {
  richTextBox1 = new RichTextBox ( ) ;
  button1 = new Button ( ) ;
  button2 = new Button ( ) ;
  button3 = new Button ( ) ;
  button4 = new Button ( ) ;
  button5 = new Button ( ) ;
  saveFileDialog1 = new SaveFileDialog ( ) ;
  openFileDialog1 = new OpenFileDialog ( ) ;
  printPreviewDialog1 = new PrintPreviewDialog ( ) ;
  printDialog1 = new PrintDialog ( ) ;
  ThePrintDocument = new PrintDocument ( ) ;
  ThePrintDocument.PrintPage  = new PrintPageEventHandler ( ThePrintDocument_PrintPage ) ;
  SuspendLayout ( ) ;
  richTextBox1.Anchor = AnchorStyles.None ;
  richTextBox1.Name = "richTextBox1" ;
  richTextBox1.Size = new Size ( 448 , 280 ) ;
  richTextBox1.TabIndex = 0 ;
  richTextBox1.Text = "" ;
  button1.Anchor = AnchorStyles.None ;
  button1.Location = new Point ( 41 , 289 ) ;
  button1.Name = "button1" ;
  button1.Size = new Size ( 48 , 30 ) ;
  button1.TabIndex = 1 ;
  button1.Text = "打开" ;
  button1.Click  = new System.EventHandler ( button1_Click ) ;
  button2.Anchor = AnchorStyles.None ;
  button2.Location = new Point ( 274 , 288 ) ;
  button2.Name = "button2" ;
  button2.Size = new Size ( 48 , 30 ) ;
  button2.TabIndex = 4 ;
  button2.Text = "预览" ;
  button2.Click  = new System.EventHandler ( button2_Click ) ;
  button3.Anchor = AnchorStyles.None ;
  button3.Location = new Point ( 108 , 288 ) ;
  button3.Name = "button3" ;
  button3.Size = new Size ( 48 , 30 ) ;
  button3.TabIndex = 2 ;
  button3.Text = "保存" ;
  button3.Click  = new System.EventHandler ( button3_Click ) ;
  button4.Anchor = AnchorStyles.None ;
  button4.Location = new Point ( 174 , 288 ) ;
  button4.Name = "button4" ;
  button4.Size = new Size ( 80 , 30 ) ;
  button4.TabIndex = 3 ;
  button4.Text = "打印机设置" ;
  button4.Click  = new System.EventHandler ( button4_Click ) ;
  button5.Anchor = AnchorStyles.None ;
  button5.Location = new Point ( 345 , 288 ) ;
  button5.Name = "button5" ;
  button5.Size = new Size ( 48 , 30 ) ;
  button5.TabIndex = 5 ;
  button5.Text = "打印" ;
  button5.Click  = new System.EventHandler ( button5_Click ) ;
  saveFileDialog1.DefaultExt = "*.txt" ;
  saveFileDialog1.FileName = "file.txt" ;
  saveFileDialog1.InitialDirectory = "c:\" ;
  saveFileDialog1.Title = "另存为!" ;
  openFileDialog1.DefaultExt = "*.txt" ;
  openFileDialog1.FileName = "file.txt" ;
  openFileDialog1.InitialDirectory = "c:\" ;
  openFileDialog1.Title = "打开文本文件!" ;
  AutoScaleBaseSize = new Size ( 6 , 14 ) ;
  ClientSize = new Size ( 448 , 325 ) ;
  this.Controls.Add ( button1 ) ;
  this.Controls.Add ( button2 ) ;
  this.Controls.Add ( button3 ) ;
  this.Controls.Add ( button4 ) ;
  this.Controls.Add ( button5 ) ;
  this.Controls.Add ( richTextBox1 ) ;
  
  this.MaximizeBox = false ;
  this.Name = "Form1" ;
  this.Text = "C#来操作文本文件" ;
  this.ResumeLayout ( false ) ;
  }
  static void Main ( )
  {
  Application.Run ( new Form1 ( ) ) ;
  }
  
  private void button1_Click ( object sender , System.EventArgs e )
  {
  try
  {
  if ( openFileDialog1.ShowDialog ( ) == DialogResult.OK )
  {
  FileStream fs = new FileStream ( openFileDialog1.FileName  , FileMode.Open , FileAccess.Read ) ;
  StreamReader m_streamReader = new StreamReader ( fs ) ;
  //使用StreamReader类来读取文件
  m_streamReader.BaseStream.Seek ( 0 , SeekOrigin.Begin ) ;
  // 从数据流中读取每一行,直到文件的最后一行,并在richTextBox1中显示出内容
  this.richTextBox1.Text = "" ;
  string strLine = m_streamReader.ReadLine ( ) ;
  while ( strLine != null )
  {
  this.richTextBox1.Text  = strLine   "n" ;
  strLine = m_streamReader.ReadLine ( ) ;
  }
  //关闭此StreamReader对象
  m_streamReader.Close ( ) ;
  }
  }
  catch ( Exception em )
  {
  Console.WriteLine ( em.Message.ToString ( ) ) ;
  }
  
  }
  
  private void button3_Click ( object sender , System.EventArgs e )
  {
  try
  {
  //获得另存为的文件名称
  if ( saveFileDialog1.ShowDialog ( ) == DialogResult.OK )
  {
  //创建一个文件流,用以写入或者创建一个StreamWriter
  FileStream fs = new FileStream ( @saveFileDialog1.FileName  , FileMode.OpenOrCreate , FileAccess.Write ) ;
  StreamWriter m_streamWriter = new StreamWriter ( fs ) ;
  m_streamWriter.Flush ( ) ;
  
  // 使用StreamWriter来往文件中写入内容
  m_streamWriter.BaseStream.Seek ( 0 , SeekOrigin.Begin ) ;
  // 把richTextBox1中的内容写入文件
  m_streamWriter.Write ( richTextBox1.Text ) ;
  //关闭此文件
  m_streamWriter.Flush ( ) ;
  m_streamWriter.Close ( ) ;
  }
  }
  catch ( Exception em )
  {
  Console.WriteLine ( em.Message.ToString ( ) ) ;
  }
  }
  
  private void button4_Click ( object sender , System.EventArgs e )
  {
  printDialog1.Document = ThePrintDocument ;
  printDialog1.ShowDialog ( ) ;
  }
  //预览打印文档
  private void button2_Click ( object sender , System.EventArgs e )
  {
  try
  {
  string strText = richTextBox1.Text ;
  myReader = new StringReader ( strText ) ;
  PrintPreviewDialog printPreviewDialog1 = new PrintPreviewDialog ( ) ;
  printPreviewDialog1.Document = ThePrintDocument  ;
  printPreviewDialog1.FormBorderStyle = FormBorderStyle.Fixed3D  ;
  printPreviewDialog1.ShowDialog ( ) ;
  }
  catch ( Exception exp )
  {
  Console.WriteLine ( exp.Message.ToString ( ) ) ;
  }
  }
  //打印richTextBox1中的内容
  private void button5_Click ( object sender , System.EventArgs e )
  {
  printDialog1.Document = ThePrintDocument ;
  string strText = richTextBox1.Text ;
  myReader = new StringReader ( strText ) ;
  if ( printDialog1.ShowDialog ( ) == DialogResult.OK )
  {
  ThePrintDocument.Print ( ) ;
  }
  }
  protected void ThePrintDocument_PrintPage ( object sender , PrintPageEventArgs ev )
  {
  float linesPerPage = 0 ;
  float yPosition = 0 ;
  int count = 0 ;
  float leftMargin = ev.MarginBounds.Left ;
  float topMargin = ev.MarginBounds.Top ;
  string line = null ;
  Font printFont = richTextBox1.Font ;
  SolidBrush myBrush = new SolidBrush ( Color.Black ) ;
  //计算每一页打印多少行
  linesPerPage = ev.MarginBounds.Height / printFont.GetHeight ( ev.Graphics ) ;
  //重复使用StringReader对象 ,打印出richTextBox1中的所有内容
  while ( count < linesPerPage && ( ( line = myReader.ReadLine ( ) ) != null ) )
  {
  // 计算出要打印的下一行所基于页面的位置
  yPosition = topMargin   ( count * printFont.GetHeight ( ev.Graphics ) ) ;
  // 打印出richTextBox1中的下一行内容
  ev.Graphics.DrawString ( line , printFont , myBrush , leftMargin , yPosition , new StringFormat ( ) ) ;
  count  ;
  }
  // 判断如果还要下一页,则继续打印
  if ( line != null )
  ev.HasMorePages = true ;
  else
  ev.HasMorePages = false ;
  myBrush.Dispose ( ) ;
  }
  }
  
  四. 总结:
  
  本文虽然只是介绍了用C#处理文本文件,但其实对C#处理其他文件也有很多的参考价值,这是因为在名字空间"System.IO"中定义的用以处理其他文件的类的结构和用以处理文本文件的类的结构有很多是相同的。希望本文对你用C#进行文件方面的编程有所帮助。

之前大部分时间都在用Visual Studio 2008做开发,虽然也点开过代码分析,但是一看一大串内容,尤其是一大串针对命名的建议,就果断关闭了。这次实习使用的Visual Studio 2012,发现代码分析默认去掉了很多内容,显示的也都是比较重要并需要改进的地方,所以也都认真研究了一下。

1.创建对象
2.使用对象
3.释放对象

第一步:创建服务框架
要创建一个新的 Windows 服务,可以从Visual C# 工程中选取 Windows 服务(Windows Service)选项,给工程一个新文件名,然后点击确定。

 

二.创建对象
1.创建对象实际分为两个步骤﹕变量类型宣告和初始化对象

你可以看到,向导向工程文件中增加WebService1.cs类:

【文章索引】

2.变量类型宣告(declare),如﹕

如下图来设置属性:

  1. 问题和解决方法
  2. 为什么这样去做
  3. 相关链接

图片 1FileStream fs

其中各属性的含意是:

 

这行代码会在当前的变量作用域空间(栈或堆)里建立一个叫做fs的变量﹐至少四个字节吧(因为要存一个对象的地址)

ü Autolog 是否自动写入系统的日志文件

【一、问题和解决方法】

3.初始化对象
对象在使用(调用其方法或属性)前﹐必须进行初始化。
如﹕

ü CanHandlePowerEvent 服务时候接受电源事件

应该有人会写如下的代码吧,为了释放资源,我们把打开的东西都关闭掉,貌似没有什么问题。

图片 2fs = new FileStream(@"C: est.txt",FileMode.OpenOrCreate);

ü CanPauseAndContinue 服务是否接受暂停或继续运行的请求

 1 FileStream fs = null;
 2 StreamReader sr = null;
 3 
 4 try
 5 {
 6     fs = new FileStream(@"F:test.txt", FileMode.Open, FileAccess.Read);
 7     sr = new StreamReader(fs);
 8 
 9     String content = sr.ReadToEnd();
10 }
11 finally
12 {
13     if (sr != null)
14     {
15         sr.Close();
16     }
17 
18     if (fs != null)
19     {
20         fs.Close();
21     }
22 }

这行代码会分成3个步骤﹕
a.在托管堆中分配一块内存﹐其大小等于FileStream中所有字段(当然不包括静态的)的内存总和加上MS认为需要的其它东东。
b.初始化对象的字段(值类型的把其位全部初始化成0,对象初始化为null﹐当然string是一个例外﹐它被初始化成空字符串)
c.调用FileStream相应的构造器﹐这里会初始化一个非托管资源(文件)的私有字段。

ü CanShutdown 服务是否在运行它的计算机关闭时收到通知,以便能够调用 OnShutDown 过程

当然,喜欢用using的同学也可能会写如下的代码:

三.使用对象
使用对象就没什么讲的﹐就是调用对象的方法(或属性等)来完成某个功能当然为了释放对象而调用的方法其范畴应不属于此类中(现在提到的Finalize等)

ü CanStop 服务是否接受停止运行的请求

1 using (FileStream fs = new FileStream(@"F:test.txt", FileMode.Open, FileAccess.Read))
2 {
3     using (StreamReader sr = new StreamReader(fs))
4     {
5         String content = sr.ReadToEnd();
6     }
7 }

四.释放对象
1.释放对象也就是说这个对象我已经不需要了﹐现在我要把其释放﹐以便把其在堆上所占用的内存空间给收回来(当然变量名的内存空间就不需要管了﹐因为它会随其作用域自动消失)

ü ServiceName 服务名

但是这两种代码如果使用代码分析会出现什么情况呢,如下图。

2. .net自动进行内存管理﹐也就是说当它判断一个对象没有用了(当然有自己的算法)﹐它就会将其内存给自动收回来﹐但是其收回的时间一般不确定(当.net认为内存紧张时﹐它就会开始)

第二步:向服务中增加功能
在 .cs代码文件中我们可以看到,有两个被忽略的函数 OnStart和OnStop。

图片 3

BTW:其实我们就是想自己收回对象的内存也不可能﹐因为MS没有提供途径(GC.Collect也是启动.net的内存收集功能)

OnStart函数在启动服务时执行,OnStop函数在停止服务时执行。在这里,当启动和停止服务时,向一个文本文件中写入一些文字信息,代码如下:

比较有意思的是,这里提示的是“不应对一个对象多次调用 Dispose”,为什么会是“一个对象”呢?

五.第一个结论
在net中使用对象很简单﹐创建对象之后直接使用就可以了﹐不用了也不要去管它﹐垃圾收集器会帮你把内存要回来的。

protected override void OnStart(string[] args)

通过翻阅MSDN中的CA2202(链接在文后),我们可以查到原因是这样的,“某个方法实现所包含的代码路径可能导致对同一对象多次调用 IDisposable.Dispose 或与 Dispose 等效的方法(例如,用于某些类型的 Close() 方法)”,MSDN中直接给出了解决方法就是不要关闭StreamReader,而是直接关闭FileStream。

六.例外
当对象的成员引用了一个非托管资源时(不在托管堆上分配的内存或资源﹐像文件﹐数据库连接等等)﹐下面以一个例子来说明﹕
System.IO.FileStream类别﹐这是.net基本类库提供的一个非托管资源(文件)封装对象(用Reflector工具反编译mscorlib.dll可见其代码)

{

 

1.FileStream毫无疑问封装了一个非托管资源

FileStream fs = new FileStream(@"d:/mcWindowsService.txt" , FileMode.OpenOrCreate, FileAccess.Write);

【二、为什么这样去做】

观其源代码发现有这样一个私有成员﹕

StreamWriter m_streamWriter = new StreamWriter(fs);

本文由糖果派对电玩城发布于用户反馈,转载请注明出处:"不要多次释放对象"的小随笔

您可能还会对下面的文章感兴趣: