关于C#一个基础问题的初步探查

关于C#一个弱弱问题的初步探查

在书上看到这样一个例子:

已知:
interface IControl
{
void Paint();
}
class Control : IControl
{
public void Paint(){ ... }
}
class TextBox : Control
{
new public void Paint(){ ... }
}

问:
IControl it = new TextBox();
it.Paint(); //调用哪个Paint()?

书上的结论是Control::Paint(),并且吹了一通什么没有virtual关键字或者让TextBox直接继承至IControl就是静态决议,什么如果Control::Paint()加上virtual又怎么怎么,如果TextBox::Paint()去掉new又怎么怎么,感觉甚是繁琐、复杂和多变,让人如坠万丈深渊,万劫不复…(- -)

就我原来从C++带来的理解:IControl是个interface,其实隐含IControl::Paint()是abstract 以及 public,只是不能显式写出;所以成员函数的调用决议应该是运行期完成,也就是说为TextBox::Paint();

实际上机测试结果,令人汗颜,他的结果的确是正确的,但是他的后续所有解释都是错误的;不管如何加virtual、去掉new,最后的结果都是一个—Control::Paint(),程序代码如下:
using System;
namespace TestInterface
{
interface IControl
{
void Paint();
}
class Control : IControl
{
public void Paint()
{
Console.WriteLine("Control::Paint()");
}
}
///

/// TextBox 的摘要说明。
///

class TextBox : Control
{
new public void Paint()
{
Console.WriteLine("TextBox::Paint()");
}
}
///

/// TestInterface 的摘要说明。
///

class TestInterface
{
///

/// 应用程序的主入口点。
///

[STAThread]
static void Main(string[] args)
{
//
// TODO: 在此处添加代码以启动应用程序
//
IControl it = new TextBox();
it.Paint();
}
}
}

如果去掉new当然仅仅会导致警告,实际上还是覆盖。

书中还提到“我们的误解来自一个假设:Control::Paint()被自动视为vitrual”,根据实际上机测试,这个假设的确是成立的,而不是什么误解,至于加上virtual和去掉virtual所带来的区别仅仅在于是否加入关键字final。如果没有加上了virtual,那么Control::Paint()会自动视为final(IL中),有例为证:

Contorl::Paint()
.method public hidebysig newslot virtual final //如果加上vitrual则这里去掉final
instance void Paint() cil managed
{
// 代码大小 11 (0xb)
.maxstack 1
IL_0000: ldstr "Control::Paint()"
IL_0005: call void [mscorlib]System.Console::WriteLine(string)
IL_000a: ret
} // end of method Control::Paint
TextBox::Paint()
.method public hidebysig instance void Paint() cil managed
{
// 代码大小 11 (0xb)
.maxstack 1
IL_0000: ldstr "TextBox::Paint()"
IL_0005: call void [mscorlib]System.Console::WriteLine(string)
IL_000a: ret
} // end of method TextBox::Paint

而在Main()中仅是简单调用IControl::Paint()

.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
.custom instance void [mscorlib]System.STAThreadAttribute::.ctor() = ( 01 00 00 00 )
// 代码大小 13 (0xd)
.maxstack 1
.locals init ([0] class TestInterface.IControl it)
IL_0000: newobj instance void TestInterface.TextBox::.ctor()
IL_0005: stloc.0
IL_0006: ldloc.0
IL_0007: callvirt instance void TestInterface.IControl::Paint()
IL_000c: ret
} // end of method TestInterface::Main

===== 华丽的分割线 =====
呵呵,以上就是我初步的分析,特别混乱,不要见笑,下面我做个总结(个人意见、仅供参考)

原来的C++到C#,在函数重载这一块有一个很明显的区别,那就是override关键字。如果重载必须显式的指明override关键字,如果没有,将隐藏基类成员。这点是很自然的,但是本例的特别之处在于interface的引入。

—– interface —–
interface默认为abstract类,但是和普通abstract类不同的是,他并没有派生自System.Object;其内接口也默认为abstract virtual如下:

.class interface private abstract auto ansi IControl
{
} // end of class IControl
.method public hidebysig newslot abstract virtual
instance void Paint() cil managed
{
} // end of method IControl::Paint

之所以interface被默认为public(指其内接口,而interface本身可以不为public),abstract就不用解释了,但是明示出来反而会出错;
—– interface end —–

—– interface implement —–
.class private auto ansi beforefieldinit Control
extends [mscorlib]System.Object
implements TestInterface.IControl
{
} // end of class Control
.method public hidebysig newslot virtual final //如果加上vitrual则这里去掉final
instance void Paint() cil managed
{
// 代码大小 11 (0xb)
.maxstack 1
IL_0000: ldstr "Control::Paint()"
IL_0005: call void [mscorlib]System.Console::WriteLine(string)
IL_000a: ret
} // end of method Control::Paint

这里太有趣了(- -,过分了…还是说稍微有点意思)interface接口的实现会在IL中自动加上virtual关键字,而不像刚才书上说的,什么误解,这是为什么呢?因为如果像本例中一样,用IControl调用Paint(),而IControl本身是不会实现Paint()的,所以他的实现必须在IL中以virtual关键字标明,让动态决议的时候,能够考虑到这个实现,不然IControl::Paint()的调用就会出问题。

至于这里提到的final,也是interface implement比较独特的一点。override会使得本身重载基类成员,并且本身成为virtual,所以自己的子类也可以重载自己。在程序中,若想重载基类成员,但又不想让子类继续重载,就会使用override sealed关键字,这里IL中的final关键字实际上就是指的sealed。

由此,我们一般写一个函数:

public void foo();

子类都无法override,除非函数改为:

virtual void foo();

但是这里由于interface implement本身需要申明为override(IL中仍为virtual),所以为了延续惯例,用sealed(IL中为final)封闭之,除非你显示的指示virtual,这样就和我们通常的观念保持一致鸟~~~~
—– interface implement end —–

—– extended classes —–
.class private auto ansi beforefieldinit TextBox
extends TestInterface.Control
{
} // end of class TextBox
extends TestInterface.Control
.method public hidebysig instance void Paint() cil managed
{
// 代码大小 11 (0xb)
.maxstack 1
IL_0000: ldstr "TextBox::Paint()"
IL_0005: call void [mscorlib]System.Console::WriteLine(string)
IL_000a: ret
} // end of method TextBox::Paint

这里不管你是否采用new关键字,实际上都起到了覆盖的作用,只是如果没有new关键字会引发警告,提醒你是否确实是想覆盖,如果你想关闭警告,就必须显示的告诉编译器我想覆盖—就是使用new关键字。

值得一提的有两点:
1、如果程序中直接写virtual而不用override,其实还是覆盖。可以从IL中看出:
程序<->IL
virtual<->newslot virtual
override<->virtual
2、如果这个类直接多重继承IControl,像这样:
class TextBox : Control, IControl
{
...
}

实际上这个类就不再属于extended classes了,而是interface implement。由于对于interface implement的选择是virtual的,所以很自然会调用TextBox::Paint();
—– extended classes end —–

如果以上的讨论正确,本例就很好解释鸟~~~

IControl::Paint()由于是virtual,他会先行寻找可行函数,再进行最佳决议;

由于new关键字的覆盖函数并未override,所以他连可行函数都算不上。自然无论如何都不可能被调用。相反,可能函数只有Control::Paint();

如果让TextBox::Paint()用override修饰(自然Control::Paint()需要用virtual修饰),那么可能函数有Control::Paint(),TextBox.Paint(),其中根据动态决议,最佳为TextBox.Paint();

ZTMD宁愿重写,也不排错

奋战了一个星期的简易登陆系统排错,最后的结果让人汗颜:
将Web.Config里面的
<authentication mode="Windows">
</authentication>

改为
<authentication mode="Forms">
<forms name="TheSky" path="/" loginUrl="/TheSky/WebModules/Users/Login.aspx" protection="All" timeout="30">
</forms>
</authentication>

我一直没有查Web.Config。唉,已经欲哭无泪了,呜呜…

ps:ztmd,ztmD,ztMd,zTmd,Ztmd,ztMD,zTmD,ZtmD,zTMd,ZtMd,ZTmd,zTMD,ZtMD,ZTmD,ZTMd,ZTMD

假期意识流

意识流

—假期生活乱纪

这个假期不堪回首,单调得可怕,除了4本书之外,记忆中仅存的就全是某大学的研究生会的网站了。网站说来更不堪回首,完全算得上“赶鸭子上架”的典型(我“可爱”的姐在网站“八字还没有一撇”的时候,居然就敢将“网站建设费”挥霍一空,后来备受挫折后,无力还债,于是栽赃到了我的头上- -!)。下面将网站建设过程中的点点滴滴乱纪下来,想到哪里写到哪里,逻辑未免混乱,各位大虾莫要见笑。目的也很简单,让各位看看一个新手眼中的网站建设过程。

1、 研究生会:

因为这个网站,我就已经堂而皇之成为某大学研究生会编制中的一员,这种“海纳百川”的“气势”令我无不拜倒- -!

2、 网站需求:

刚开始的网站需求很“明确”—“美观、大方、展现当代研究生风采”。让我感到在这种“派头”的指导老师手下干起事情来一定比较“自由”,但是后来事实证明我错了,这种指导老师也有开始变“聪明”的时候。

需求增加了一条—“采用最新的Asp.Net技术,并且务必使用SQL Server最新版本”,我在深入理解这条需求之后,对于各个软件、框架的版本甚是犹豫,不知beta版是否也计算在内- -!

3、 美工:

这个我没有发言权,反正阿梅和阿楠看过(因为当时我不会用PS分页- -!)

4、 VS .NET环境:

由于以前习惯于Borland的BCB,于是“疏”在于没RAD就不活了,“密”在于任何东西都希望有根有据,简单明了。对于VC++6.0从未涉猎,因为当时第一印象就觉得不像是拿来编程的,加之MFC的封装没有一定的实力,定会感到极其莫名其妙,那消息映射的编程方式更是使得局势错综复杂,不像VCL里面虽然依然有很多不懂,但至少容易把握过程。

介于上述原因,所以当我观之Visual Studio .NET的时候还是大吃一惊,惊叹微软也终于RAD了一把。于是看了本《C# Primer》上手就干。

但是现实总是残酷的,虽然很多东西感觉用起很顺手,但还是有很多极其不习惯:

首当其冲的就是一个解决方案里面的类、函数等等,不用.h,不用#include好像就能够在各个文件中可见,原因可能是采用namespace的组织方式,但是老是觉得没有#include <*.h>就是不爽(我理想中的世界应该是把文件include过来后,再在namespace里面避免污染,而不要在一个解决方案里面的文件到处都可见似的)。

其次就是所谓的大名鼎鼎的“Code Behind”,这个倒是在很大程度上解决了网页程序设计中的版权问题,并且使代码和外观分离,便于和传统窗口编程靠拢。但是对于新手而言,在不具备驾驭的能力的情况下,无非是场灾难,代码直接生成DLL,不用export,不用声明,只需assembly一下,就可以使用,随便咋看都极其莫名其妙。

介于以上,我终于放弃了使用VS .NET,本来想选择同出Borland之手的C# Builder,但是令人郁闷的是没有找到注册码- -!所以最后我还是选择了以前写ASP程序时候采用的Dreamweaver+EditPlus+MSDN,本来记事本足已,但是少了调试器,要是连行号都看不到,那我还是直接跳楼算了。

5、 风格:

回头看我写的代码,感觉很多地方看起来还是像用ASP写的:P,不过因为毕竟是ASP.NET的东西,还是初步感受了一下ASP.NET的便利,诸如:DataGrid、DataList、Page_Load、还有在连接SQL数据库时候的便利等等等等。

6、 语言:

这个是我最无语的一点,由于C# 是刚学的,所以开始的时候还是在尝试VB,在花了很长时间都没有发现下面代码为什么通不过之后,我还是痛下决心用C# 吧- -!

#region code
<script language=”VB” runat=server>
sub foo(object sender,eventargs e)
{
//----------
}
</script>
#endregion

接踵而来的转化工作,幸好还不算很困难,但确实很烦,下面将我在转化时的主要工作总结一下:

a) 大小写:VB里面的标识符不区分大小写,如:page_load、datagrid也可以通过,但是到了C# 里面就必须区分大小写了(这里是改得最郁闷的了,对照到MSDN一个一个挨到改,想当初写VB Script的时候不偷懒就好了)

b) 索引(Item):VB里面使用object(index)就可以索引了,但是到了C#里面索引可必须使用object[index]符号,不过如果你使用Item调用就一致了,不过新手一般都不会那样做。

其他可能还有很多转化工作,不过我倒是幸运的没有碰到。

7、 基本代码组织方式:

由于那个网站大量的都是带图像文本处理,所以基本上前台采用的是把分页后形成的静态页面里面的动态部分挖空,以iframe填充之,iframe的src里面要不就指向list.aspx文件(显示前多少项,以及more按钮),并传入类别、css、行数、列数作为参数控制外观;要不就指向multilist.aspx文件(显示所有内容,以DataGrid进行分页控制),传入的外观控制参数一致。并且二者都接受hot=1参数,若设置了,则排序的方式由order by subdate desc变为order by 点击次数 desc

8、 前台:

前台如前述方式架构,我觉得最有用的就是DataGrid与DataList的基本使用及外观控制,如果实在觉得不方便,新手还是用<ItemTemplate>来得快:),另外在进行数据绑定的时候,我基本上就只用到了下面一条语句:

<%# ((DataRowView)Container.DataItem)["foo"] %>

9、 后台管理:

a)FreeTextBox:

说起后台管理自然想到了熟悉的DormForce Blog的后台,那里的那个输入控件,令人感觉甚爽,在IE里面察看源,发现名字叫做FreeTextBox- -!这个名字实在是…

于是Google后下载了一个,感觉封装得很好,使用也很方便,直接register就可以用了,令我倍感ASP.NET还是很爽的,也对Web Control产生了一丝眷恋。但是问题也出来了,他的“插入图片”按钮令人极其郁闷,只能插入图片URL,而没有提供上传,本来想在FTB控件旁边单独提供一个UploadImg按钮,提供上传然后返回URL,供使用FTB插入之需。

但是在看了“宝玉”的文章后突然发现FTB对于Button提供了接口ToolBarButton,于是可以把上传的工作集成到按钮里面。用宝玉提供的方法,在VS .NET里面“依样画葫芦”很快便搞定了,其实就是直接从ToolBarButton直接继承构造新的Button就可以了。

但是问题是我没有用Code Behind的方式,而且找了半天也没有发现,如果不用Code Behind,把类的定义写在哪里?所以只有自己想办法,看了半天英文版的FTB帮助,终于找到了方法:

<FTB:TOOLBARBUTTON title="插入图片"
runat="server" ScriptBlock="myOpenFunc();" ButtonImage="insertimage" />

其中的ButtonImage指定名称后会自动从aspnet_client目录调用图片资源,若要更换可以直接替换文件即可,不过注意保持长宽比(不然会很难看的)。另外我感觉aspnet_client这个名字很可疑,可能有特殊的用途,诸如用于客户端的啥子东西,没有敢改名字:(

其中的ScriptBlock直接在FTB生成的Html页面里面加入内容,如上例就生成如下咚咚:

… = new FTBButton(… function(){myOpenFunc();} …)…

myOpenFunc()为调用此FTB控件页面上<script>内定义的函数。藉由此实现runat=server代码对于本机函数的调用,即通常所谓的Response.Write法。

另外,我还需要从本机函数调取runat=server中控件的属性。想了半天,找到了如下的不是办法的办法:

function myOpenFunc(){
var dt=eval('myForm.myDataTable');
var tempurl='../inc/uploadimg.aspx?tab=';
tempurl = tempurl+dt.value;
window.open(tempurl,'_uploadimg','width=300 height=50');
}

其中myForm、myDataTable分别为runat=server里面Form和DropDownList的Id名称,使得本机获得的html页面里的form和select的id为这二者,从而为本机函数的实现提供可能性。

在uploadimg.aspx中只需完成上传文件,调用opener.top.InsertText(…)方法传入图片地址即可,而在myOpenFunc()所在页面中定义InsertText方法完成插入。

b) 编辑区域:

编辑区域的实质,宝玉已经说得比较清楚了,其实就是一个document.designMode设为on的Iframe。因此可以方便的用本机函数进行各种编辑工作以及调用focus()获取焦点。

c) DropDownList的数据绑定:

由于需要,选定一个DropDownList后要对另外一个重新进行数据绑定,实现方式如下:

指定DropDownList的属性OnSelectedIndexChanged=”SelectionChangeFunc” AutoPostBack=”true”,SelectionChangeFunc会传送给一个delegate型别从而起到类似函数指针的作用。

void SelectionChangeFunc(Object sender, System.EventArgs e)
{
myDataClass.DataSource = CreateDataSource(...);
myDataClass.DataTextField = "TextField";
myDataClass.DataValueField = "ValueField";
myDataClass.DataBind();
myDataClass.SelectedIndex = 0;
}
ICollection CreateDataSource(…)
{
System.Data.DataTable myDt = new System.Data.DataTable();
myDt.Columns.Add(new System.Data.DataColumn("TextField", typeof(String)));
myDt.Columns.Add(new System.Data.DataColumn("ValueField", typeof(String)));
...
myDt.Rows.Add(CreateRow("…", "…", myDt));
...
System.Data.DataView myDv = new System.Data.DataView(dt);
return myDv;
}
System.Data.DataRow CreateRow(String Text, String Value, System.Data.DataTable dt)
{
System.Data.DataRow myDr = dt.NewRow();
dr[0] = Text;
dr[1] = Value;
return myDr;
}

d) DataGrid的CommandButton:

column字段:<asp:ButtonColumn HeaderText="Del Button"
Text="Del" CommandName="DelBtn" />

指定DataGrid属性OnItemCommand=”Grid_DelCommand”

通过Grid_DelCommand函数处理所有的CommandButton信息,而Grid_DelCommand函数只需根据CommandName指定的值选择操作即可。

void Grid_DelCommand(Object sender, DataGridCommandEventArgs e)
{
if(((LinkButton)e.CommandSource).CommandName == "DelBtn")
{
...
}
}

这里有两个问题:

其一,好像DataGrid被提交的时候也会调用此OnItemCommand函数,因为当时由于只有一个command,没有用if判断CommandName值的时候,一提交就要出错,而且是在这个函数里面出错的。所以建议就算只有一个command也用if后者select判断一下。

其二,我本来是把<asp:ButtonColumn>定义为“PushButton”的,但是在进行强制类型转换的时候没有找到相应的类型,不管是Button、PushButton都不对,所以只好换用默认的LinkButton才调试通过。

e) DataGrid的分页:

设置DataGrid的

AllowPaging=True
PagerStyle-Mode=NumericPages
PagerStyle-HorizontalAlign=Right
OnPageIndexChanged=”Change_Page_Index”属性

定义下列函数:

void Change_Page_Index(Object sender, DataGridPageChangedEventArgs e)
{
myDataGrid.CurrentPageIndex=e.NewPageIndex;
...
myDataGrid.DataBind();
}
void PagerButtonClick(Object sender, EventArgs e)
{
String arg = ((LinkButton)sender).CommandArgument;
switch(arg)
{
case ("next"):
if (myDataGrid.CurrentPageIndex < (myDataGrid.PageCount - 1))
myDataGrid.CurrentPageIndex ++;
break;
case ("prev"):
if (myDataGrid.CurrentPageIndex > 0)
myDataGrid.CurrentPageIndex --;
break;
case ("last"):
myDataGrid.CurrentPageIndex = (myDataGrid.PageCount - 1);
break;
default:
myDataGrid.CurrentPageIndex = Convert.ToInt32(arg);
break;
}
myDataGrid.DataBind();
}

这里注意的是换了页要重新DataBind()一下。

f) SQL:

不多说了,下面是我觉得常用的函数:

System.Data.DataSet MyQuery(String querystring)
{
String connectionString = @"...";
System.Data.IDbConnection dbConnection = new
System.Data.SqlClient.SqlConnection(connectionString);
System.Data.IDbCommand dbCommand = new
System.Data.SqlClient.SqlCommand();
dbCommand.CommandText = querystring;
dbCommand.Connection = dbConnection;
System.Data.IDbDataAdapter dataAdapter = new
System.Data.SqlClient.SqlDataAdapter();
dataAdapter.SelectCommand = dbCommand;
System.Data.DataSet dataSet = new System.Data.DataSet();
dataAdapter.Fill(dataSet);
return dataSet;
}
int MyDelete(String delString)
{
String connectionString = @"...";
System.Data.IDbConnection dbConnection = new
System.Data.SqlClient.SqlConnection(connectionString);
System.Data.IDbCommand dbCommand = new
System.Data.SqlClient.SqlCommand();
dbCommand.CommandText = delString;
dbCommand.Connection = dbConnection;
int rowsAffected = 0;
dbConnection.Open();
try
{
rowsAffected = dbCommand.ExecuteNonQuery();
}
finally
{
dbConnection.Close();
}
return rowsAffected;
}
int MyInsert(...)
{
String connString = "...";
System.Data.IDbConnection dbConnection = new
System.Data.SqlClient.SqlConnection(connString);
String queryString = "INSERT INTO [...]([...], ...) VALUES (";
queryString +="@..., ...)";
System.Data.IDbCommand dbCommand = new
System.Data.SqlClient.SqlCommand();
dbCommand.CommandText = queryString;
dbCommand.Connection = dbConnection;
System.Data.IDataParameter dbParam_... = new
System.Data.SqlClient.SqlParameter();
dbParam_...ParameterName = "@...";
dbParam_...Value = ...;
dbParam_...DbType = System.Data.DbType.String;
dbCommand.Parameters.Add(dbParam_...);
...
int rowsAffected = 0;
dbConnection.Open();
try {
rowsAffected = dbCommand.ExecuteNonQuery();
}
finally {
dbConnection.Close();
}
return rowsAffected;
}

10、 其他点点滴滴:

a) 去掉图片加链接后外包框:

<img border=0>

b) 去掉IE图片悬停工具栏:

<img galleryimg=”no”>或者<meta http-equiv="imagetoolbar" content="no">

c) 去掉链接点击后的虚线:

<a href="…" onFocus="this.blur()"></a>

d) 使iframe自适应长宽:

<iframe src="..."
onload="this.height =
this.document.body.offsetHeight;this.width=this.document.body.offsetWidth;">
</iframe>

另外千万注意不要漏了</iframe>,网页被撑开了,我查了半天才发现是这个原因。

e) 连续滚动:

一般的marquee会两次滚动间会有一段时间的空白,采用此处的代码可以使得其连续滚动。

上面这些就是这个网站的点点滴滴,没有什么技术含量,只是写篇乱纪,以标明这个颓废的寒假。