- 抽象类是否可继承接口?
抽象类里面可以包括抽象方法和非抽象方法,而接口只是定义了方法的原型,没有方法的具体定义。从这方面来分析,抽象类是可以继承接口的。
- 抽象类继承接口后,一定要实现接口中的成员方法么?
答案是否定的。
抽象类继承接口后,可以实现接口中的成员方法,也可以不实现。如果不实现接口中成员方法,只需将该方法声明为abstract类型就可以了。
interface ITemp
{
void IM();
}
abstract class CTemp : ITemp
{
public abstract void IM();
}
- (抽象)类继承类和接口的顺序是如何的?
class A
{
}
interface IA
{
}
interface IB
{
}
能否像下面一样定义一个类?
class B : IA, IB, A
{
}
答案是不可以。编译器会告诉你:基类A必须在任何接口之前。
所以应该写成:
class B : A, IA, IB
{
}
这是为什么呢?
下面是一家之言,仅供参考,不能保证所说的正确性。如果有误,敬请斧正。
针对上面的例子,假设类A中有一个方法时M,接口IA中同时声明了一个方法M。如果类B是按照IA, IB, A这样的顺序继承,则在B类型执行内存分配时,B对象的方法表里面首先加载接口IA中的方法的实现,之后是IB的,再之后是A的。因为A中也有方法M,所以这样的顺序是不是会认为M是IA中成员M的实现呢?如果类B是按照A, IA, IB这样的顺序继承,就不存在这样的情况了。
- 接口可以继承抽象类么?
接口只定义成员方法的原型,并不包括成员方法的实现。抽象类里面可能有实现的方法。如果说,接口可以继承抽象类,那这就和“接口只声明成员方法的原型,而没有具体方法的定义”相违背。
所以说,接口不能继承抽象类。
- 接口中可以包含属性和事件,但是不可以包含字段。
属性和事件在编译的时候编译器会自动将其转化成方法。
- 接口中不能再声明类型。例如接口中不能再定义类,不能再声明接口等等,但是类里面可以再定义类,再声明接口。
例如下面是合理的:
class N
{
interface IU { }
class CU { }
}
而
interface IB
{
interface IU { }
class CU { }
}
或者
interface IB
{
interface IU { }
}
是通不过编译的。
- 类如何做到隐藏接口中的实现方法?
如果一个类实现了一个接口中的方法M,则M应该声明为public,否则编译无法通过。但是如何让类的引用无法使用接口中的方法M呢?例如
http://images.cnblogs.com/cnblogs_com/edenia/243832/r_HideMethodsFromInterface.JPG
看上图,你会发现对象a的方法列表中并没有方法M。如何做到呢?
http://images.cnblogs.com/cnblogs_com/edenia/243832/r_HideMethodsFromInterface_1.JPG
是的,通过接口名称限定类中实现的接口中的方法。
像上面那样,如何访问到M呢?通过接口类型的引用访问即可,例如IA a = new CA();。
再讨论这样做是否有意义?
接口是为不相关的类提供通用的功能的,像上面,如果M对类CA的引用是隐藏的,我们完全可以将M这样的方法脱离出来再定义一个接口就哦可了。所以照这样说,上面那种做法是没有必要的。
CLR类型系统CTS(Common Type System)将类型分为两种:值类型与引用类型。对应简单值的类型称为值类型,对应传统“对象”的类型称为引用类型。
值类型直接包含数据,值类型的实例分配在堆栈或者内联结果上面,值类型的实例不需要进行垃圾回收。CLR针对C#语言内置了15种类型:object、string、char、bool、decimal、byte、sbyte、short、ushort、int、uint、long、ulong、float、double。
引用类型包括自描述类型、指针类型以及接口类型。其中自描述类型又分为类类型和数组。而类类型又分为自定义类类型、装箱的值类型以及委托类型。
除了枚举类型以外的值类型的直接基类型是System.ValueType,而枚举类型直接继承于System.Enum,System.Enum又继承于System.ValueType。除了接口外的数据类型均继承于System.Object。
引用类型的实例是分配在托管堆上面,在对象不再被引用时,GC堆将会对其进行回收,但是对象的引用是分配在堆栈上面的。
下面主要说struct类型。
struct是值类型,继承于System.ValueType。struct类型可以像class类型一样拥有自己的方法、字段,也可以实现接口,但不支持继承。但是不能给C#的struct指定显示的基类型,即隐含的基类型总是System.ValueType。不能将struct声明为abstract或者sealed,编译器将隐式地添加sealed修饰符。结构的值存在堆栈或者内联上。
AnyTao“字符串驻留”一文链接地址:http://www.cnblogs.com/anytao/archive/2008/08/27/must_net_22.html
昨天,看到AnyTao的这篇文章时是第一次听说“字符串驻留”,呵呵,真惭愧。
AnyTao没有在文章中给出8个测试的结果原因,下面是我自己的分析。
1.
static void Main()
{
string s1 = "abc";
Console.WriteLine(string.IsInterned(s1) ?? "null");
}
结果输出为:abc
分析:首先“??”符号也是在AnyTao文章中看到的,也是我第一次知道,该符号为二元运算符,如果“??”的左边不为空则返回符号左边实际应返回的数据(如果左边不是计算得到的那就直接返回左边的内容),否则返回“??”符号右边的内容。
其次就是“字符串驻留”。当CLR初始化时,它会创建一个内部的散列表,其中的键为字符串,值为指向托管堆中字符串对象的引用。刚开始,此散列表为空,当JIT编译器编译方法时,它会在散列表中查找每一个文本常量字符串,如果查找到则返回散列表中该常量字符串对应的值(也即托管堆中对象的引用);如果找不到,则在托管堆中构造一个新的string对象指向该常量字符串,然后将该常量字符串和指向该对象的引用添加到散列表里面。
而string.IsInterned()方法判断指定参数是否在散列表里面,该方法接收一个string作为参数,并会在CLR内部的散列表中查找它。如果CLR内部的散列表中含有该字符串,IsInterned将返回散列表中保存的字符串的对象引用;否则,将返回null,而且不会静该字符串添加到散列表中。
所以,当编译执行到string s1 = "abc";时,CLR首先会在托管堆构造一个新的string对象(指向“abc”),然后将文本常量字符串“abc”和指向s1对象的引用添加到散列表里面。然后string.IsInterned(s1)判断s1是在散列表里面的,所以会输出“abc”。
记住,CLR的内部散列表里面保存的是源代码的文本常量字符串。CLR的内部散列表是在进程中的所有AppDOMAIN(应用程序域)不再引用这些字符串对象时,垃圾收集器才对其进行释放。
2.
static void Main()
{
string s1 = "ab";
s1 += "c";
Console.WriteLine(string.IsInterned(s1) ?? "null");
}
输出结果:null
分析:
如图,通过查看ILDASM发现,执行ldstr的只是两个常量字符串"ab"和"c",所以字符串”abc”并不在散列堆里面。所以输出结果为”null”。
3.
static void Main()
{
string s1 = "abc";
string s2 = "ab";
s2 += "c";
string s3 = "ab";
Console.WriteLine(string.IsInterned(s1) ?? "null");
Console.WriteLine(string.IsInterned(s2) ?? "null");
Console.WriteLine(string.IsInterned(s3) ?? "null");
}
输出结果:
abc
abc
ab
通过1、2的分析,我们知道编译结束时散列表里面的文本常量字符串有:”abc”、”ab”、”c”。
string.IsInterned(s1)和string.IsInterned(s3)就不用说了,答案显而易见。说一下string.IsInterned(s2),s2的结果是”abc”,而”abc”在散列表里面是存在的,所以string.IsInterned(s2)结果不为空,将返回会返回字符串”abc”的引用
,我想应该是s1的地址吧。所以Console.WriteLine(string.IsInterned(s2) ?? "null"); 输出”abc”。
针对问题3,我增加了一个对比示例,如下:
static void Main()
{
string s2 = "ab";
s2 += "c";
string s3 = "ab";
Console.WriteLine(string.IsInterned(s2) ?? "null");
Console.WriteLine(string.IsInterned(s3) ?? "null");
}
输出结果:
null
ab
这样的输出结果与AnyTao问题3的结果区别的原因应该比较清楚了吧?O(∩_∩)O~
4.
static void Main()
{
string s1 = "abc";
string s2 = "ab";
string s3 = s2 + "c";
Console.WriteLine(string.IsInterned(s3) ?? "null");
}
输出结果:abc
这个由前面的分析可以看出结果,在此不再分析。
5.
static void Main()
{
string s2 = "ab";
s2 += "c";
Console.WriteLine(string.IsInterned(s2) ?? "null");
string s1 = "abc";
}
输出结果:
abc
分析:不管string s1 = "abc";是放在Console.WriteLine(string.IsInterned(s2) ?? "null"); 前面还是后面,在编译的时候,文本常量字符串"abc"都被放到了CLR的散列表里面,所以Console.WriteLine(string.IsInterned(s2) ?? "null");输出”abc”是无容置疑的~。
6.
static void Main()
{
tring s2 = "ab";
s2 += "c";
Console.WriteLine(string.IsInterned(s2) ?? "null"); //null
string s1 = GetStr();
}
private static string GetStr() { return "abc"; }
输出结果:null
分析:虽然函数GetStr()返回的是”abc”,但是散列表里面只存在”ab”和”c”。
7.
public const string s1 = "abc";
static void Main()
{
string s2 = "ab";
s2 += "c";
Console.WriteLine(string.IsInterned(s2) ?? "null");
}
输出结果:null
分析:
先看一下ILDASM的结果吧:
由此看出散列表里面存在的是”ab”和”c”两个文本常量。所以输出结果是”null”。
8.
public static string s1 = "abc";
static void Main()
{
string s2 = "ab";
s2 += "c";
Console.WriteLine(string.IsInterned(s2) ?? "null");
}
输出结果:abc
分析:为什么7输出null,而8输出abc呢?什么原因?呵呵,一个是常量一个是静态变量。对比7的反编译我们看一下8的,如图:
我们看到,8的反编译结果里面多了一个函数.cctor,它是类型构造器,在类中存在编译时的静态成员初始化时,类会自动添加一个私有的构造函数,反编译后IL里面体现为.cctor,而.ctor是构造函数。我们由.cctor得内容可以看得出”abc”文本常量字符串放入了散列表里面,所以输出结果为”abc”。
补充:
字符串驻留机制基于String对象的恒定不变性,有助于节省内存。对于动态创建的字符串(比如:string+variable;variable+variable),驻留机制便不起作用,因为驻留机制仅是将源代码中的文本常量字符串放入散列表里面的。而诸如string+variable;variable+variable这些是调用了concact()函数,这可以由反编译代码看出。
感谢:
感谢AnyTao,是你的分享,让我提高了自己。class A
{
public A()
{
PrintFields();
}
public virtual void PrintFields() { }
}
class B : A
{
int x = 1;
int y;
public B()
{
y = -1;
}
public override void PrintFields()
{
Console.WriteLine("x={0},y={1}", x, y);
}
}
class Program
{
static void Main(string[] args)
{
B b = new B();
//A a=new A();
//a.PrintFields();
b.PrintFields();
Thread.Sleep(100000);
}
}
看了AnyTao的按值传递与按引用传递,我感觉自己以前好像并没有弄明白到底什么是按值传递,什么是按引用传递。以前,误以为:传递的参数为引用类型的就是按引用传递。其实,这是一个误解。
下面先以一个例子分析:
public class COboject
{
public int i = 10;
}
示例一:直接对象的成员的值
public class Form1
{
private void Form1_Load(object sender, EventArgs e)
{
COboject abf = new COboject();
MessageBox.Show(abf.i.ToString());//10
ChangeValue(abf);//50
MessageBox.Show(abf.i.ToString());//50
}
private void ChangeValue(COboject cobj)
{
cobj.i = 50;
MessageBox.Show(cobj.i.ToString());
}
}
用内存图示进行分析:
说明一点,值类型与引用类型内存分配的不同是值类型分配在线程堆栈中,引用类型分配在托管堆中,但是线程堆栈中会分配该引用类型在托管堆中的地址。
所以,调用ChangeValue()函数时在线程堆栈中执行了对abf对象的拷贝。
示例二:改变对象的引用(表面上改变)
为什么后面括号里面写上“表面上改变”呢,原因是调用函数的对象实质上未改变,只是被调用函数内改变了。
public class Form1
{
private void Form1_Load(object sender, EventArgs e)
{
COboject abf = new COboject();
MessageBox.Show(abf.i.ToString());//10
ChangeObject(abf);//50
MessageBox.Show(abf.i.ToString());//10
}
private static void ChangeObject(COboject cobj)
{
COboject tempObj = new COboject();
tempObj.i = 50;
cobj = tempObj;
MessageBox.Show(cobj.i.ToString());
}
}
用内存图示进行分析:
说明:对象在被调用函数内的指针发生变化了,但是这个对象只是调用函数内的拷贝,所以调用函数内的对象地址不变。
示例三:调用函数内对象的真正改变
public class Form1
{
private void Form1_Load(object sender, EventArgs e)
{
COboject abf = new COboject();
MessageBox.Show(abf.i.ToString());//10
ChangeRef(ref abf);//50
MessageBox.Show(abf.i.ToString());//50
}
private static void ChangeRef(ref COboject cobj)
{
COboject tempObj = new COboject();
tempObj.i = 50;
cobj = tempObj;
MessageBox.Show(cobj.i.ToString());
}
使用ref关键字,告诉编译器,我传递的是对象的地址的地址,所以当在被调用函数内部更改对象的地址时,调用函数的对象地址也改变了。具体怎么画这个内存图示,虽然脑袋里面有个概念,可是不知道怎么画了,不好意思,大家能理解意思就好。如果有哪位高手可以不吝赐教,给出改图示,LZ在此大谢特谢!!!
这篇废话不多说,直接上代码。
首先说明,通讯过程中的异常均不进行处理(连接异常除外),由超时重发控制。
一、获取SOCKET连接类TimeOutSocket
public class TimeOutSocket
{
private static bool IsConnectionSuccessful = false;//连接是否成功
private static Exception socketexception;
private static ManualResetEvent TimeoutObject = new ManualResetEvent(false);
public static Socket Connect(IPAddress ipAddress, int port, int timeoutMSec)
{
TimeoutObject.Reset();//将事件设为非终止状态,阻止线程
socketexception = null;
Socket temp = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//异步连接
temp.BeginConnect(new IPEndPoint(ipAddress, port), new AsyncCallback(CallBackMethod), temp);
if (TimeoutObject.WaitOne(timeoutMSec, false))
{
//在连接超时时间范围内等待信号量为真则返回SOCKET
if (IsConnectionSuccessful)
{
return temp;
}
else
{
throw new TimeoutException("连接远程主机失败!请检查网络连接是否通畅。");
}
}
else
{
//关闭连接,并释放所有相关资源
temp.Close();
throw new TimeoutException("连接超时!请检查网络连接是否通畅。");
}
}
/// <summary>
/// 异步连接成功后回调函数
/// </summary>
/// <param></param>
private static void CallBackMethod(IAsyncResult asyncresult)
{
try
{
IsConnectionSuccessful = false;
Socket client = asyncresult.AsyncState as Socket;
//结束挂起的异步连接请求
client.EndConnect(asyncresult);
IsConnectionSuccessful = true;
}
catch (Exception ex)
{
IsConnectionSuccessful = false;
socketexception = ex;
}
finally
{
//允许其它线程继续
TimeoutObject.Set();
}
}
}
二、通讯变量(常量)
private const int ConnectTimeOut = 6000;//连接超时时间,以ms为单位
private byte[] data = new byte[1024];//接收字节数组
private int recvSize = 1024;//接收数据个数
private static int XH = 1;//发送的命令序号
//下面两个类库ConvertLibrary和DealProtocolData是我封装的类库,在后续中将逐一给出
ConvertLibrary convertData = new ConvertLibrary();//对数据提供转换支持
DealProtocolData dealProtocolData = new DealProtocolData();//按要求处理协议结果
三、委托
委托,实质上就是指向函数的指针,也是一个类。
delegate void DealRecvMsgHandler(string str);//用于处理接收到的数据
上面一句用ILDASM反编译后如下:
可以看出,系统默认生成了其构造函数(.ctor是构造函数),以及成员函数BeginInvoke、EndInvoke以及Invoke。自己定义的委托继承于System.MulticastDelegate(反编译中extends看出)。
四、异步接收数据回调函数ReceivData()
/// <summary>
///异步接收数据回调
/// </summary>
/// <param></param>
private void ReceivData(IAsyncResult iar)
{
Socket remote = (Socket)iar.AsyncState;
int recv = 0;
try
{
recv = remote.EndReceive(iar);//异常:远程主机强迫关闭了一个现有的连接
}
catch
{
}
string tempStr = convertData.ByteToHex(data, recv);//字节数组转化成16进制
if (tempStr.Length > 0)
{
// DealRecvMsg数据处理函数
DealRecvMsgHandler handler = new DealRecvMsgHandler(DealRecvMsg);
handler.Invoke(tempStr);//同步数据处理
}
else
{
//由于用了第三方的串口转网口模块,所以有时候会收到空的情况,这时继续接收
try
{
remote.BeginReceive(data, 0, recvSize, SocketFlags.None, new AsyncCallback(ReceivData), PublicCommunication.client);
}
catch { }
}
}
五、异步发送数据回调函数(SendData)
/// <summary>
///异步发送数据回调
/// </summary>
/// <param></param>
private void SendData(IAsyncResult iar)
{
Socket remote = (Socket)iar.AsyncState;
try
{
int send = remote.EndSend(iar);
currentSendedOrderDate = DateTime.Now;//记录下发送时间,用于重发时使用
if (listenerTimer.Enabled == false)// listenerTimer用于超时重发
listenerTimer.Enabled = true;
currentSendedOrderEnum = GetSendedOrderType(sendMsg);//获取已发送命令类型
currentSendedOrderXH = GetSendedOrderXH(sendMsg);//获取已发送命令序号
//发送后,异步接收
remote.BeginReceive(data, 0, recvSize, SocketFlags.None, new AsyncCallback(ReceivData), PublicCommunication.client);
}
catch
{
}
}
六、接收数据处理函数(只判断命令的合法性,决定下一个命令。具体的数据需要另需委托进行处理。)
private void DealRecvMsg(string orgTempStr)
{
//这里面是一个while循环,循环的条件是命令长度大于某个最小指,每次循环按命令包中命令长度取出命令,并将剩余的接收到的命令用于循环。
//由于通讯追循一定的流程,所以在这里由接收到的命令可以决定下一个命令
//大致给一下这里面的程序
while (orgTempStr.Length >= 12)
{
try
{
string tempStr = orgTempStr.Substring(0, int.Parse(convertData.ConvertString(orgTempStr.Substring(2, 2), 16, 10)) * 2);//取出一条命令
orgTempStr = orgTempStr.Substring(int.Parse(convertData.ConvertString(orgTempStr.Substring(2, 2), 16, 10)) * 2);// orgTempStr保留剩余的命令
//判断数据合法性:根据协议接收到的数据长度至少为12,而且保证接收到的数据无误(校验码), GetStrXOR()函数是获取校验码,后面给出
if (tempStr.Length >= 12
&& dealProtocolData.GetStrXOR(tempStr.Substring(0, tempStr.Length - 2)).Equals(tempStr.Substring(tempStr.Length - 2), StringComparison.OrdinalIgnoreCase))
{
string workState = tempStr.Substring(8, 2);//获取仪器工作状态
string order = tempStr.Substring(4, 2);//获取命令字
//如果不是上次发送的命令则丢弃当前数据
if (!JudgeReceiveOrderWithSendOrder(order, currentSendedOrderEnum))
continue;
//命令序号不同则丢弃
if (!(int.Parse(convertData.ConvertString(tempStr.Substring(6, 2), 16, 10)) == currentSendedOrderXH))
continue;
currentSendedOrderDate = DateTime.MinValue;//收到命令后,设置命令的发送时间为DateTime的最小值
ReSendTimeCount = -1;//超时重发次数
//"00"应该定义成常量,方便使用时如果"00"改为”ff”时不用满程序中修改
if (workState.Equals("00", StringComparison.OrdinalIgnoreCase))//仪器正常工作
{
if (order.Equals(ProtocolContent.SetParamsCommandWord, StringComparison.OrdinalIgnoreCase))//设置参数命令的返回
{
//决定下一个要发送的命令
sendMsg =……;
}
………..
………..
byte[] msg = convertData.HexToByte(sendMsg);
try
{
//异步发送数据
PublicCommunication.client.BeginSend(msg, 0, msg.Length, SocketFlags.None, new AsyncCallback(SendData), PublicCommunication.client);
//处理发送序号,保证发送序号在1-255之间
XH = (XH + 1) % 256;
if (XH == 0)
XH = 1;
}
catch
{
}
}
七、超时重发
/// <summary>
/// 命令的枚举值
/// </summary>
public enum OrderEnum
{
UnKnown = 0
}
private static string sendMsg = String.Empty;//记录每次发送的命令
private static OrderEnum currentSendedOrderEnum = OrderEnum.UnKnown;//记录本次发送的命令的类型
private static int currentSendedOrderXH = -1;//当前发送命令的序号
private static DateTime currentSendedOrderDate = DateTime.MinValue;//最近一次发送命令的时间
private System.Timers.Timer listenerTimer;//用于监控命令发送的定时器
private static int ReSendTimeCount = -1;//重新发送命令的次数
listenerTimer = new System.Timers.Timer(25);
listenerTimer.Elapsed += new System.Timers.ElapsedEventHandler(listenerTimer_Elapsed);
listenerTimer.Enabled = false;
private void listenerTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
if (currentSendedOrderDate != DateTime.MinValue)//当没有收到数据时
{
double timeInterval = (DateTime.Now - currentSendedOrderDate).TotalMilliseconds;
if (timeInterval >= 1000)//1S重发
{
ReSendTimeCount++;
if (ReSendTimeCount >= 3)
{
//超时处理
……..
return;
}
//未超时三次,继续重发
SendMsgWhenTimeOut(sendMsg);
}
}
}
八、通讯结束或者超时后的处理
当无通讯时,客户端关闭,服务器是检测不到的,所以我在做的时候,一次通讯结束或者超时后都会将连接关闭并置为空。
private void WhenCommunicateOver()
{
listenerTimer.Enabled = false;
try
{
PublicCommunication.client.Close();
}
catch { }
finally
{
PublicCommunication.client = null;
}
}
九、尝试三次连接
private void startCommunication()
{
int connectTime = 0;
do
{
try
{
PublicCommunication.client = TimeOutSocket.Connect(IPAddress.Parse(PublicModel.InstrumentIP), PublicModel.InstrumentPort, ConnectTimeOut);
break;
}
catch (Exception ec)
{
connectTime++;
PublicCommunication.client = null;
if (connectTime >= 3)
{
WhenCommunicateOver();
lock (whetherShowErrorMsg)
{
if (!((bool)whetherShowErrorMsg))
{
MessageBox.Show(this, ec.Message, "系统提示", MessageBoxButtons.OK, MessageBoxIcon.Error);
whetherShowErrorMsg = true;
}
}
Application.DoEvents();
return;
}
}
}
while (connectTime < 4);
}


