java在线聊天项目0.5版 解决客户端向劳动器端发送新闻时只可以发送2回难点 OutputStreamWriter DataOutputStream socket.getOutputStream()

来源:http://www.oschina.net/translate/top-10-mistakes-that-c-sharp-programmers-make

从没解决难题以前客户端代码:

关于C#

package com.swift;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.BufferedWriter;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.net.UnknownHostException;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;

public class ChatClientFrame2 extends JFrame {

    private static final long serialVersionUID = -118470059355655240L;
    Socket s=null;
    JLabel label_shang=new JLabel();
    JLabel label_xia=new JLabel();
    JTextField tf = new JTextField(38);
    JTextArea ta = new JTextArea(15, 50);
    JButton button=new JButton();

    public ChatClientFrame2() {
        setBounds(200, 200, 500, 400);
        setTitle("客户端聊天工具 —— 0.4");
        //对窗口进行大的布局,分为三行一列,在pBasic面板上添加三个面板shang zhong xia
        JPanel pBasic=new JPanel();
        pBasic.setLayout(new BorderLayout());//不设置默认也是这种布局模式
        setContentPane(pBasic);//把面板放在窗口上,不记得用this.关键字
        JPanel shang=new JPanel();
        JPanel zhong=new JPanel();
        JPanel xia=new JPanel();
        //设置JPanel面板的大小
        shang.setSize(470, 25);
        zhong.setSize(470, 180);
        xia.setSize(470, 40);
        pBasic.add(shang,BorderLayout.NORTH);
        pBasic.add(zhong,BorderLayout.CENTER);
        pBasic.add(xia,BorderLayout.SOUTH);
        shang.setBackground(Color.red);
        zhong.setBackground(Color.yellow);
        xia.setBackground(Color.blue);
        /*
         * 三个面板,上边放一个标签“聊天记录”,中间放一个文本域,
         * 下边分为左中右——分别放标签“输入信息“,文本框和”发送“按钮
         */
        label_shang.setText("聊天记录");
        shang.add(label_shang);
        ta.setLineWrap(true);// 自动换行
        JScrollPane scroll=new JScrollPane(ta);// 增加滚动条,以便不增加行数
        zhong.add(scroll);
        label_xia.setText("输入信息");
        xia.add(label_xia,BorderLayout.WEST);
        /*
         * 增加功能,窗口监听事件,窗口打开时设置光标焦点在tf文本域中
         */
        this.addWindowListener(new WindowAdapter() {
            @Override
            public void windowOpened(WindowEvent e) {
                tf.requestFocus();
            }
        });
        xia.add(tf,BorderLayout.CENTER);
        button.setText("发送");
        xia.add(button,BorderLayout.EAST);
        /*
         * 增加“发送”按钮的功能,增加回车的功能,监听相同,
         * 则使用内部类实现,以提高代码复用性
         */
        final class ShareListener implements ActionListener{

            @Override
            public void actionPerformed(ActionEvent e) {
                String taText=ta.getText();
                String tfText=tf.getText()+"\r\n";
                ta.setText(taText+tfText);
                tf.setText("");
                //当回车或发送按钮时,tfText发送到服务器
                try {
                    //可以尝试用DataOutputStream里的writeUTF方法
//                    DataOutputStream ds=new DataOutputStream(s.getOutputStream());
//                    ds.writeUTF(tfText);
//                    ds.flush();
//                    ds.close();
                    BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(s.getOutputStream(),"utf-8"));
                    bw.write(tfText);
                    bw.close();
                } catch (IOException e1) {
                    // TODO Auto-generated catch block
                    e1.printStackTrace();
                }

            }
        }
        button.addActionListener(new ShareListener());
        tf.addActionListener(new ShareListener());
        //通过压缩自动调整各个面板
        pack();
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);// 点关闭按钮同时退出程序
        setVisible(true);
        //创建窗体直接调用连接服务器
        connect();
    }

    /*
     * 增加一个连接到服务器的方法
     */
    public void connect() {
        try {
            s=new Socket("127.0.0.1",8888);
            System.out.println("connected!");
        } catch (UnknownHostException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        // 别忘了创建窗体对象,也可以用生成对象调用其他的方法如launchFrame()
        new ChatClientFrame2();
    }

}

C#是达到微软国有语言运行库(CL凯雷德)的个别言语中的一种。实现CL奥迪Q7的语言能够受益于其带来的特征,如跨语言集成、相当处理、安全性增强、部件组合的简便模型以及调节和剖析服务。作为当代的CL本田CR-V语言,C#是使用最为普遍的,其行使场景针对Windows桌面、移入手提式有线电话机以及服务器环境等繁杂、专业的支付项目。

尚无缓解难点此前服务端代码:

C#是种面向对象的强类型语言。C#在编写翻译和周转时都有个别强类型检查,使在多数第一名的编制程序错误可以被尽早地发现,而且地方固定一定精准。相比较于那三个不拘泥类型,在违规操作很久后才报出可追踪到莫明其妙错误的语言,那能够为程序员节省多如牛毛光阴。然则,许多程序员有意或无意识地遗弃了这些检测的有些,那导致本文中商量的一些题材。

package com.swift;

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

public class ChatServer {

    public static void main(String[] args) {
        try {
            ServerSocket ss = new ServerSocket(8888);
            for (;;) {
                Socket s = ss.accept();
                //当有连接,则显示,用于测试
                System.out.println("a client connected success");
                //可以尝试用DataInputStream中readUTF方法
//                DataInputStream dis=new DataInputStream(s.getInputStream());
//                String str=dis.readUTF();
//                dis.close();

                BufferedReader br=new BufferedReader(new InputStreamReader(s.getInputStream(),"utf-8"));
                String str=br.readLine();
                System.out.println(str);
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}

有关本文

演示进程

本文描述了十一个 C# 程序员常犯的荒谬,或相应幸免的牢笼。

葡京在线开户 1

尽管本文商量的多数谬误是对准 C# 的,有些错误与任何以 CLPRADO为目的的言语,可能使用了 Framework Class Library (FCL) 的语言也不无关系。

出现难题:


java.net.SocketException: Socket is closed

广泛错误 #1: 把引用当做值来用,恐怕反过来

葡京在线开户 2

C++
和任何不少言语的程序员,习惯了给变量赋值的时候,要么赋单纯的值,要么是长存对象的引用。可是,在C#
中,是值依旧援引,是由写这些目的的程序员决定的,而不是实例化对象并赋值的程序员决定的。那往往会坑到
C# 的新手程序员。

 

只要你不精晓你正在利用的指标是或不是是值类型或引用类型,你大概会赶上一些惊喜。例如:


  Point point1 = new Point(20, 30);
  Point point2 = point1;
  point2.X = 50;
  Console.WriteLine(point1.X);       // 20 (does this surprise you?)
  Console.WriteLine(point2.X);       // 50

  Pen pen1 = new Pen(Color.Black);
  Pen pen2 = pen1;
  pen2.Color = Color.Blue;
  Console.WriteLine(pen1.Color);     // Blue (or does this surprise you?)
  Console.WriteLine(pen2.Color);     // Blue

 若是不关闭流

如您所见,固然Point和Pen对象的开创情势同样,不过当八个新的X的坐标值被分配到point2时,
point1的值保持不变
。而当2个新的color值被分配到pen2,pen1也随即更改。由此,大家得以想见point1和point2每一种都包蕴自个儿的Point对象的副本,而pen1和pen2引用了同1个Pen对象
。假若没有那个测试,大家怎么能够领悟这么些规律?

把那句删掉bw.close();

一种格局是去看一下指标是什么样定义的(在Visual
Studio中,你能够把光标放在对象的名字上,并按下F12键)

是足以的,程序不会出错,但总要关闭,能够在客户端窗口关闭的时候实施那句。

  public struct Point { … }     // defines a “value” type
  public class Pen { … }        // defines a “reference” type

如上所示,在C#中,struct关键字是用来定义八个值类型,而class关键字是用来定义引用类型的。
对于这个有C++编程背景人来说,借使被C++和C#时期某个类似的重庆大学字搞混,或者会对以上那种表现感到很受惊。

 

一经你想要信赖的行事会因值类型和引用类型而异,举例来说,假设你想把三个对象作为参数字传送给叁个格局,并在那些主意中修改那些指标的状态。你肯定要保证您在拍卖正确的品类对象。

三番八次运转载现还是不得不发送一条消息,但并未出错提醒。那时为何吧?


查阅程序意识,当每便点击回车或然发送按钮,都会有一条音讯发送,但服务器正是从未出示,因为客户端发送了数十四回,服务端只读取了一遍,所以显得一条,

常见的失实#2:误会未初步化变量的暗中同意值

上边代码是读取多次,展现多次:

在C#中,值得类型无法为空。依据定义,值的项目值,甚至先河化变量的值类型必须有一个值。那正是所谓的该品种的默许值。这一般会招致以下,意想不到的结果时,检查1个变量是或不是未发轫化:

package com.swift;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

public class ChatServer {

    public static void main(String[] args) {
        try {
            ServerSocket ss = new ServerSocket(8888);
            for (;;) {
                Socket s = ss.accept();
                //当有连接,则显示,用于测试
                System.out.println("a client connected success");

                BufferedReader br=new BufferedReader(new InputStreamReader(s.getInputStream(),"utf-8"));
                //问题:客户端多次发送信息,而服务器端只读取了一次,所以只输出一次,多次输出下面的办法也不好
                String str=br.readLine();
                System.out.println(str);
                String str1=br.readLine();
                System.out.println(str1);
                String str2=br.readLine();
                System.out.println(str2);
                String str3=br.readLine();
                System.out.println(str3);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}
  class Program {
      static Point point1;      static Pen pen1;      static void Main(string[] args) {
          Console.WriteLine(pen1 == null);      // True
          Console.WriteLine(point1 == null);    // False (huh?)
      }
  }

 接收了八遍

何以不是【point
1】空?答案是,点是三个值类型,和暗中认可值点(0,0)一样,没有空值。
那是1个格外简单和广泛的不当。在C#黑龙江中国广播集团大(然而不是百分百)值类型有二个【IsEmpty】属性,你能够看看它是或不是等于暗许值:

能够增添3个死循环来最为接收

Console.WriteLine(point1.IsEmpty);        // True

葡京在线开户 3


打响消除接收次数难题,可前边的关门不可能实施了

大面积错误 #3: 使用不适于或未内定的办法比较字符串

修改服务端代码如下:

在C#中有为数不少办法来相比字符串。

葡京在线开户 4

固然如此有恒河沙数程序员使用==操作符来相比较字符串,不过这种方法其实是最不引进应用的。首要缘由是由于那种措施没有在代码中显式地钦赐使用哪连串型去相比较字符串。
相反,在C#中判断字符串是或不是等于最好使用Equals方法:


public bool Equals(string value);  



public bool Equals(string value, StringComparison comparisonType);

 

率先个Equals方法(没有comparisonType那参数)和采纳==操作符的结果是相同的,但利益是,它显式的指明了比较类型。它会按顺序逐字节的去相比较字符串。在广大景况下,这多亏你所期待的相比类型,尤其是当相比一些由此编制程序设置的字符串,像文件名,环境变量,属性等。在那个景况下,只要按顺序逐字节的可比就足以了。使用不带comparisonType参数的Equals方法开始展览相比较的唯一不佳的地点在于那个读你程序代码的人只怕不领会你的可比类型是什么。

0.5版效果图如下:

利用带comparisonType的Equals方法去相比字符串,不仅会使你的代码更清楚,还会使你去考虑清楚要用哪类档次去比较字符串。那种办法十一分值得您去行使,因为就算在立陶宛(Lithuania)语中,按顺序进行的可比和按语言区域拓展的对比之间并从未太多的区分,然而在其余的一部分语种或者会有十分大的不比。要是你忽略了那种或许性,无疑是为您本身在未来的道路上挖了众多“坑”。举例来说:

葡京在线开户 5

  string s = "strasse";

  // outputs False:
  Console.WriteLine(s == "straße");
  Console.WriteLine(s.Equals("straße"));
  Console.WriteLine(s.Equals("straße", StringComparison.Ordinal));
  Console.WriteLine(s.Equals("Straße", StringComparison.CurrentCulture));        
  Console.WriteLine(s.Equals("straße", StringComparison.OrdinalIgnoreCase));

  // outputs True:
  Console.WriteLine(s.Equals("straße", StringComparison.CurrentCulture));
  Console.WriteLine(s.Equals("Straße", StringComparison.CurrentCultureIgnoreCase));

0.5版服务端代码:

最安全的施行是连接为Equals方法提供贰个comparisonType的参数。

package com.swift;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

public class ChatServer {

    public static void main(String[] args) {
        boolean started=false;
        try {
            ServerSocket ss = new ServerSocket(8888);
            started=true;
            while(started) {
                boolean connected=false;
                Socket s = ss.accept();
                System.out.println("a client connected success");
                connected=true;
                BufferedReader br=new BufferedReader(new InputStreamReader(s.getInputStream(),"utf-8"));
                while(connected) {
                String str=br.readLine();
                System.out.println(str);
                }
                br.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

上边是一对主导的点拨原则:

0.5版客户端代码:(扩展disconnect()方法,放入输出流和端口的关门,优化输出流创设次数)

  • 当相比较用户输入的字符串只怕将字符串相比结实展现给用户时,使用本地化的可比(CurrentCulture
    或者CurrentCultureIgnoreCase)。
  • 当用于程序设计的相比字符串时,使用原有的可比(Ordinal 大概OrdinalIgnoreCase)
  • InvariantCulture和InvariantCultureIgnoreCase一般并不选用,除非在受限的情状之下,因为本来的可比一般作用更高。若是与地点文化相关的相比是须求的,它应当被实施成基于当下的文化或然另一种卓殊文化的比较。
package com.swift;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.BufferedWriter;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.net.UnknownHostException;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;

public class ChatClientFrame extends JFrame {

    private static final long serialVersionUID = -118470059355655240L;
    Socket s=null;
    BufferedWriter bw=null;
    JLabel label_shang=new JLabel();
    JLabel label_xia=new JLabel();
    JTextField tf = new JTextField(38);
    JTextArea ta = new JTextArea(15, 50);
    JButton button=new JButton();

    public ChatClientFrame() {
        setBounds(200, 200, 500, 400);
        setTitle("客户端聊天工具 —— 0.5");
        //对窗口进行大的布局,分为三行一列,在pBasic面板上添加三个面板shang zhong xia
        JPanel pBasic=new JPanel();
        pBasic.setLayout(new BorderLayout());//不设置默认也是这种布局模式
        setContentPane(pBasic);//把面板放在窗口上,不记得用this.关键字
        JPanel shang=new JPanel();
        JPanel zhong=new JPanel();
        JPanel xia=new JPanel();
        //设置JPanel面板的大小
        shang.setSize(470, 25);
        zhong.setSize(470, 180);
        xia.setSize(470, 40);
        pBasic.add(shang,BorderLayout.NORTH);
        pBasic.add(zhong,BorderLayout.CENTER);
        pBasic.add(xia,BorderLayout.SOUTH);
        shang.setBackground(Color.red);
        zhong.setBackground(Color.yellow);
        xia.setBackground(Color.blue);

        label_shang.setText("聊天记录");
        shang.add(label_shang);
        ta.setLineWrap(true);// 自动换行
        JScrollPane scroll=new JScrollPane(ta);// 增加滚动条,以便不增加行数
        zhong.add(scroll);
        label_xia.setText("输入信息");
        xia.add(label_xia,BorderLayout.WEST);
        /*
         * 增加功能,窗口监听事件,窗口打开时设置光标焦点在tf文本域中
         */
        this.addWindowListener(new WindowAdapter() {
            @Override
            public void windowOpened(WindowEvent e) {
                tf.requestFocus();
            }
        });
        xia.add(tf,BorderLayout.CENTER);
        button.setText("发送");
        xia.add(button,BorderLayout.EAST);

        final class ShareListener implements ActionListener{

            @Override
            public void actionPerformed(ActionEvent e) {
                String taText=ta.getText();
                String tfText=tf.getText()+"\r\n";
                ta.setText(taText+tfText);
                tf.setText("");
                //当回车或发送按钮时,tfText发送到服务器
                try {
                    bw.write(tfText);
                    bw.flush();
                } catch (IOException e1) {
                    e1.printStackTrace();
                }

            }
        }
        button.addActionListener(new ShareListener());
        tf.addActionListener(new ShareListener());
        //通过压缩自动调整各个面板
        pack();
        this.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                //disconnect();//出错
                System.exit(0);
            }
        });
        setVisible(true);
        //创建窗体直接调用连接服务器
        connect();
    }

    public void connect() {
        try {
            s=new Socket("127.0.0.1",8888);
            System.out.println("connected!");
            bw=new BufferedWriter(new OutputStreamWriter(s.getOutputStream(),"utf-8"));

        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public void disconnect() {
        try {
            s.close();
            bw.close();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }


    public static void main(String[] args) {
        new ChatClientFrame();
    }

}

此外,对Equals
方法来说,字符串也不足为怪提供了Compare方法,能够提供字符串的争执顺序音信而不仅仅测试是还是不是等于。这么些方法能够很好适用于<,
<=, >和>= 运算符,对上述议论同样适用。

 


广泛误区 #4: 使用迭代式 (而不是表明式)的语句去操作集合

在C#
3.0中,LINQ的引入改变了大家过去对聚集对象的查询和修改操作。从那未来,你应有用LINQ去操作集合,而不是透过迭代的办法。

一些C#的程序员甚至都不亮堂LINQ的存在,万幸不掌握的人正在逐步回落。可是还某个人误以为LINQ只用在数据库查询中,因为LINQ的要害字和SQL语句实在是太像了。

即便如此数据库的查询操作是LINQ的叁个特别卓绝的运用,可是它同样能够行使于各个可枚举的联谊对象。(如:任何达成了IEnumerable接口的靶子)。举例来说,即使你有2个Account类型的数组,不要写成下边那样:

decimal total = 0;  
foreach (Account account in myAccounts) 
{    
     if (account.Status == "active") 
     {
        total += account.Balance;
     }
}

您假若这么写:

  decimal total = (from account in myAccounts
               where account.Status == "active"
               select account.Balance).Sum();

虽说那是三个很简短的例子,在有个别情况下,2个纯粹的LINQ语句能够随意地更迭掉你代码中二个迭代循环(或嵌套循环)里的几十条语句。更少的代码平时意味着发生Bug的时机也会更少地被引入。然则,记住,在性能方面只怕要权衡一下。在性质很重庆大学的地方,尤其是您的迭代代码能够对您的汇聚举办就算时,LINQ做不到,所以毫无疑问要在那三种情势之间相比一下性质。


#5常见错误:在LINQ语句之中没有考虑底层对象

对于拍卖抽象操纵集合职责,LINQ无疑是天翻地覆的。无论他们是在内部存款和储蓄器的靶子,数据库表,也许XML文书档案。在那样1个完美世界中间,你不要求了然底层对象。不过一旦大家生存在二个宏观世界中间恐怕会拉动错误。事实上,当在精确的同样数量上推行时,如若该多少碰巧在三个不一的格式之中相同的,LINQ语句能回到分化的结果

比如,请考虑上面包车型地铁言辞:

decimal total=(from accout in myaccouts
    where accout.status=='active'
    select accout.Balance).sum();

设想一下,该指标之一的账号会生出什么样。状态卓殊“Active”(注意大写A)?

可以吗,如若myaccout是Dbset的靶子。(暗许设置了差别组别轻重缓急写的布置),where表明式仍会合营该因素。但是,要是myaccout是在内存阵列之中,那么它将不般配,因而将发出分裂的总的结果。

等一会,在大家在此之前研究过的字符串比较中, 大家看见 ==
操作符扮演的角色便是回顾的可比. 所以,为啥在那么些标准下, ==
表现出的是别的的1个方式呢 ?

答案是,当在LINQ语句中的基础对象都引用到SQL表中的数据(如与在这几个例子中,在实业框架为DbSet的目的的动静下),该语句被转换来3个T-SQL语句。然后依据的T-SQL的规则,而不是C#的平整,所以在上述意况下的比较截至是不区分轻重缓急写的。

一般情况下,固然LINQ是三个惠及的和一致的主意来查询对象的聚集,在实际中您还索要理解您的言语是不是会被翻译成什么,来确定保障您的代码的行事将如预期运维。


大面积错误 #6:对增添方法感到疑忌可能被它的款式诈骗行为

就好像先前事关的,LINQ状态正视于IEnumerable接口的完结指标,比如,下边包车型地铁简易函数会商讨帐户集合中的帐户余额:

public decimal SumAccounts(IEnumerable<Account> myAccounts) 
{      
   return myAccounts.Sum(a => a.Balance);
}

葡京在线开户,在上头的代码中,myAccounts参数的品类被声称为IEnumerable<Account>,myAccounts引用了3个Sum
方法 (C# 使用类似的 “dot notation”
引用方法恐怕接口中的类),大家希望在IEnumerable接口中定义二个Sum()方法。可是,IEnumerable没有为Sum方法提供其余引用并且唯有如下所示的精简定义:

 public interface IEnumerable<out T> : IEnumerable 
 {
      IEnumerator<T> GetEnumerator();
 }

可是Sum方法应该定义到哪儿?C#是强类型的语言,由此只要Sum方法的引用是没用的,C#编写翻译器会对其报错。大家明白它必须存在,可是应当在哪儿吧?其余,LINQ提供的供查询和聚集结果有所办法在哪里定义呢?

答案是Sum并不在IEnumerable接口内定义,而是二个

定义在System.Linq.Enumerable类中的static方法(叫做“extension method”)

 namespace System.Linq 
 {
    public static class Enumerable 
    {      ...
      // the reference here to “this IEnumerable<TSource> source” is
      // the magic sauce that provides access to the extension method Sum
      public static decimal Sum<TSource>(this IEnumerable<TSource> source,Func<TSource, decimal> selector);      ...
    }
 }

扩大方法的鲜明特点是率先个形参前的this修饰符。这便是编写翻译器知道它是三个恢弘方法的“奥妙”。它所修饰的参数的花色(这些例子中的IEnumerable)表明那些类照旧接口将呈现完毕了这几个形式。

(其它部须求要提出的是,定义扩张方法的IEnumerable接口和Enumerable类的名字间的相似性没什么奇怪的。那种相似性只是随便的风骨选用。)

驾驭了那点,大家能够看来地点介绍的sumAccounts方法能以上边包车型大巴法子达成:

  public decimal SumAccounts(IEnumerable<Account> myAccounts) 
  {
      return Enumerable.Sum(myAccounts, a => a.Balance);
  }

实质上大家大概曾经这么实现了那个艺术,而不是问怎么着要有增加方法。扩充方法自个儿只是C#的一个方便人民群众你无需后续、重新编写翻译只怕涂改原始代码就可以给已存的在类型“添加”方法的法子。

扩充方法通过在文书开头添加using
[namespace];引入到作用域。你要求领悟您要找的扩张方法所在的名字空间。假如您通晓您要找的是何等,这一点很不难。

当C#编译器遇到三个目的的实例调用了三个艺术,并且它在那些目的的类中找不到丰裕方式,它就会尝试在功能域中存有的恢弘方法里找贰个匹配所须要的类和办法签名的。假设找到了,它就把实例的引用当做第二个参数字传送给那多少个扩大方法,然后一旦有别的参数的话,再把它们依次传入扩充方法。(要是C#编写翻译器没有在成效域中找到相应的扩展方法,它会抛措。)

对C#编写翻译器来说,扩大方法是个“语法糖”,使咱们能把代码写得更清晰,更便于维护(多数情况下)。鲜明,前提是您知道它的用法,不然,它会比较易于令人迷惑,尤其是一早先。

接纳扩充方法真的有优势,但也会让那么些对它不打听依然认识不正确的开发者头疼,浪费时间。特别是在看在线示例代码,或然别的已经写好的代码的时候。当这个代码发生编写翻译错误(因为它调用了这多少个分明没在被调用类型中定义的法门),一般的倾向是考虑代码是不是采纳于所引述类库的别样版本,甚至是分裂的类库。很多时刻会被花在找新本子,只怕被认为“丢失”的类库上。

在壮大方法的名字和类中定义的法门的名字同样,只是在措施签名上有微小差其他时候,甚至那个耳熟能详扩张方法的开发者也偶尔犯上边的错误。很多日子会被花在追寻“不设有”的拼写错误上。

在C#中,用增添方法变得更其流行。除了LINQ,在此外四个来自微软前日被大面积采纳的类库Unity
Application Block和Web API
framework中,也使用了扩充方法,而且还有许多其余的。框架越新,用扩张方法的只怕越大。

理所当然,你也得以写你协调的扩张方法。可是必须意识到尽管扩展方法看起来和其余实例方法同样被调用,但那事实上只是幻觉。事实上,扩大方法不可能访问所扩大类的私有和爱慕成员,所以它不能够被当作古板一而再的替代品。


广阔错误 #7: 对手头上的职务使用不当的集聚类型

C#提供了多量的聚集类型的目的,上边只列出了里面包车型地铁一有的:

Array,ArrayList,BitArray,BitVector32,Dictionary<K,V>,HashTable,HybridDictionary,List<T>,NameValueCollection,OrderedDictionary,Queue, Queue<T>,SortedList,Stack, Stack<T>,StringCollection,StringDictionary.

可是在稍微情况下,有太多的取舍和尚未丰盛的取舍一样不好,集合类型也是如此。数量很多的选项余地肯定能够确定保证是你的行事正常运作。可是你最好依然花一些时间提前查找并了然一下聚众类型,以便选择1个最契合您须求的成团类型。那最后会使你的次序品质更好,减弱失误的大概。

一旦有3个集结钦命的要素类型(如string或bit)和你正在操作的平等,你最好优先选项采纳它。当钦点相应的要素类型时,那种集合的频率更高。

为了选用好C#中的类型安全,你最好选用选取3个泛型接口,而不是应用非泛型的借口。泛型接口中的成分类型是您在在评释对象时钦定的门类,而非泛型中的成分是object类型。当使用3个非泛型的接口时,C#的编写翻译器不能够对您的代码进行项目检查。同样,当你在操作原生类型的会见时,使用非泛型的接口会造成C#对那一个连串进行频仍的装箱(boxing)和拆箱(unboxing)操作。和使用钦点了卓殊类型的泛型集合比较,那会带来很显著的质量影响。

另二个大规模的骗局是上下一心去达成3个成团类型。那并不是说永远不要那样做,你能够经过应用或扩展.NET提供的有的被广泛使用的联谊类型来节省多量的年月,而不是去重新造轮子。
特别是,C#的C5 Generic Collection Library
和CLI提供了不少附加的晤面类型,像持久化树形数据结构,基于堆的先行级队列,哈希索引的数组列表,链表等以及越来越多。


周边错误#8:遗漏资源自由

CL福睿斯托管环境扮演了垃圾堆回收器的剧中人物,所以你不供给显式释放已创立对象所占据的内部存款和储蓄器。事实上,你也无法显式释放。C#中平昔不与C++
delete对应的运算符大概与C语言中free()函数对应的法子。但那并不代表你能够忽略全数的选取过的靶子。许多目的类型封装了诸多别的类型的系统财富(例如,磁盘文件,数据连接,网络端口等等)。保持这个财富采纳状态会能够耗尽系统的财富,削弱质量并且最终导致程序出错。

固然全体C#的类中都定义了析构方法,可是销毁对象(C#中也称之为终结器)也许存在的题材是你不明确它们在时候被调用。他们在今后贰个不明确的小时被垃圾回收器调用(一个异步的线程,此举只怕引发额外的产出)。试图制止那种由垃圾回收器中GC.Collect()方法所施加的威迫限制并非一种好的编制程序实践,因为大概在垃圾回收线程试图回收适宜回收的靶未时,在不足预感的时光内导致线程阻塞。

那并代表最好不要用终结器,显式释放财富并不会造成当中的别的三个结局。当您打开3个文件、互连网端口恐怕数额连接时,当您不再采用那几个能源时,你应当及早的显式释放那个财富。

财富走漏大致在颇具的环境中都会抓住关怀。但是,C#提供了一种健康的编写制定使能源的行使变得简单。假设合理施用,能够大大收缩泄流露现的机率。NET
framework定义了三个IDisposable接口,仅由二个Dispose()构成。任何实现IDisposable的接口的对象都会在目的生命周期甘休调用Dispose()方法。调用结果肯定而且决定性的刑释解教占用的财富。

假定在贰个代码段中开创并释放四个指标,却遗忘调用Dispose()方法,那是不足原谅的,由此C#提供了using语句以担保无论代码以怎么样的格局退出,Dispose()方法都会被调用(不管是尤其,return语句,可能不难的代码段甘休)。那几个using和事先涉嫌的在文书初阶用来引入名字空间的等同。它有此外多个众多C#开发者都并未发现的,完全不相干的指标,也正是保险代码退出时,对象的Dispose()方法被调用:

  using (FileStream myFile = File.OpenRead("foo.txt")) {
    myFile.Read(buffer, 0, 100);
  }

在地点示例中运用using语句,你就能够明显myFile.Dispose()方法会在文件使用完事后被及时调用,不管Read()方法有没有抛至极。


大面积错误 #9: 回避很是

C#在运营时也会强制举办项目检查。相对于像C++那样会给错误的类型转换赋3个随机值的言语来说,C#那足以使你更快的找到出错的地方。然则,程序员再3回无视了C#的这一特征。由于C#提供了两类别型检查的法门,一种会抛出尤其,而另一种则不会,那很只怕会使他们掉进那个“坑”里。某个程序员倾向于回避十分,并且认为不写
try/catch 语句能够节约一些代码。

例如,上面演示了C#中开始展览彰显类型转换的三种分裂的方法:

  // 方法 1:
  // 如果 account 不能转换成 SavingAccount 会抛出异常
  SavingsAccount savingsAccount = (SavingsAccount)account;

  // 方法 2:
  // 如果不能转换,则不会抛出异常,相反,它会返回 null
  SavingsAccount savingsAccount = account as SavingsAccount;

很明显,假诺不对方法壹回来的结果开展判定的话,最后很可能会生出八个NullReferenceException
的十一分,那说不定会并发在稍晚些的时候,那使得问题更难追踪。比较的话,方法1会立即抛出1个InvalidCastExceptionmaking,那样,难题的来源就很令人惊叹了。

其余,就算你知道要对方法2的重回值举办判定,假设您意识值为空,接下去你会如何做?在那几个措施中告诉错误合适呢?假设类型转换失利了你还有任何的措施去尝尝吧?假使没有的话,那么抛出那一个那么些是绝无仅有正确的采取,并且尤其的抛出点离其发出点越近越好。

上边包车型大巴事例演示了别的一组广泛的主意,一种会抛出卓殊,而另一种则不会:

  int.Parse();     // 如果参数无法解析会抛出异常
  int.TryParse();  // 返回bool值表示解析是否成功

  IEnumerable.First();           // 如果序列为空,则抛出异常
  IEnumerable.FirstOrDefault();  // 如果序列为空则返回 null 或默认值

有点程序员认为“万分有剧毒”,所以她们大势所趋的觉得不抛出特别的程序显得愈发“高大上”。即便在有个别情形下,那种看法是不错的,但是那种看法并不适用于具有的气象。

举个具体的例子,有些情状下当极度发生时,你有另四个可选的格局(如,暗中同意值),那么,选取不抛出十分的方法是1个相比较好的选取。在这种境况下,你最相近上面那样写:

  if (int.TryParse(myString, out myInt)) 
  {
      // use myInt
  }
  else 
  {    
      // use default value
  }

而不是如此:

  try 
  {
    myInt = int.Parse(myString);    // use myInt
  }
  catch (FormatException) 
    // use default value
  }

然而,那并不表达 TryParse
方法更好。有些情状下适合,某个意况下则不符合。这正是怎么有二种方法供我们选用了。依据你的具体处境选取适合的办法,并记住,作为3个开发者,至极是截然能够成为你的恋人的。


广阔错误 #10: 累积编译器警告而不处理

以此破绽百出并不是C#所特有的,不过在C#中这种状态却相比多,特别是从C#编写翻译器弃用了适度从紧的类型检查之后。

警示的面世是有原因的。全部C#的编写翻译错误都标明你的代码有欠缺,同样,一些告诫也是如此。那两者之间的分别在于,对于警告的话,编译器能够依据你代码的指令工作,可是,编写翻译器发现你的代码有一点小意思,很有可能会使你的代码不能遵照你的预料运转。

七个广大的事例是,你改改了您的代码,并移除了对一些变量的施用,不过,你忘了移除该变量的注明。程序能够很好的运作,但是编写翻译器会提醒有未利用的变量。程序可以很好的运营使得一些程序员不去修补警告。更有甚者,有些程序员很好的应用了Visual
Studio中“错误列表”窗口的隐藏警告的效能,很简单的就把警告过滤了,以便专注于错误。不用多久,就会积累一堆警告,那么些警告都被“惬意”的马虎了(更糟的是,隐藏掉了)。

只是,假使您忽略掉这一类的警告,类似于上边那一个事例迟早会并发在您的代码中。

  class Account 
  {

      int myId;      
      int Id;   // 编译器已经警告过了,但是你不听

      // Constructor
      {          
         this.myId = Id;     // OOPS!
      }

  }

再拉长使用了编辑器的智能感知的成效,那种不当就很有大概爆发。

后天,你的代码中有了叁个严重的一无所长(不过编译器只是出口了1个警告,其缘由现已表明过),那会浪费你多量的光阴去寻觅那错误,具体情形由你的主次复杂程度决定。假使您一先河就注意到了这些警示,你只要求5分钟就足以修改掉,从而防止这么些难点。

记住,如若你仔细看的话,你会发现,C#编写翻译器给了你多多有关您程序健壮性的管用的新闻。不要忽略警告。你只需花几分钟的小时就足以修复它们,当现身的时候就去修补它,那能够为你节省成千成万时日。试着为本身培育一种“洁癖”,让Visual
Studio 的“错误窗口”一直展现“0不当,
0警告”,一旦出现警示就感到不爽快,然后即刻把警告修复掉。

本来了,任何规则都有区别。所以,有个别时候,固然你的代码在编写翻译器看来是多少难题的,可是那便是你想要的。在那种很少见的状态下,你最好应用
#pragma warning disable [warning id]
把吸引警告的代码包裹起来,而且只包裹警告ID对应的代码。那会且只会幸免对应的警戒,所以当有新的警戒产生的时候,你还是会清楚的。.
总结

C#是一门强大的同时很灵巧的言语,它有不少建制和言语专业来明显的坚实你的生产力。和此外语言一样,借使对它能力的摸底有限,那很可能会给您带来阻碍,而不是好处。正如一句谚语所说的那样“knowing
enough to be
dangerous”(译者注:意思是自以为已经了然丰富了,能够做某事了,但其实不是)。

熟悉C#的一部分重庆大学的分寸之处,像本文中所提到的这几个(但不压制那么些),能够扶持我们更好的去行使语言,从而幸免有些常见的圈套。