[原文作者]:Lucian Wischik
[原文链接]:System.Diagnostics.Process: redirect StandardInput, StandardOutput, StandardError
有时我们运行一个外部适用程序,向里面输入数据然后获取它的输出。这往往很容易发生这样的死锁:
' BAD CODE
Using p As New System.Diagnostics.Process
p.StartInfo.FileName = "cat"
p.StartInfo.UseShellExecute = False
p.StartInfo.RedirectStandardOutput = True
p.StartInfo.RedirectStandardInput = True
p.Start()
p.StandardInput.Write("world" & vbCrLf & "hello")
' 这里将发生死锁,如果P写到输出接口的量达到12k
p.StandardInput.Close()
Dim op = p.StandardOutput.ReadToEnd()
p.WaitForExit()
p.Close()
Console.WriteLine("OUTPUT:") : Console.WriteLine(op)
End Using
这段程序将发生死锁,因为“Cat”首先从标准输入接口中读取,然后写到标准输出接口,最后再读取它,这样一直循环下去直到没有任何东西可读取。但是如果它的标准输出接口已经填满了,并且没有对象去读取,这样就不能继续往里面写东西,从而出现阻塞。
这里的12k仅是一个随机数,我不会关注于它...
' BAD CODE
Using p As New System.Diagnostics.Process
p.StartInfo.FileName = "findstr"
p.StartInfo.UseShellExecute = False
p.StartInfo.RedirectStandardOutput = True
p.StartInfo.RedirectStandardError = True
p.Start()
' deadlock here if p needs to write more than 12k to StandardError
Dim op = p.StandardOutput.ReadToEnd()
Dim err = p.StandardError.ReadToEnd()
p.WaitForExit()
Console.WriteLine("OUTPUT:") : Console.WriteLine(op)
Console.WriteLine("ERROR:") : Console.WriteLine(err)
End Using
MSDN文档中说:你可以通过异步读取操作来避免这些依赖和潜在的死锁;或者你可以通过创建两个线程,让其中一个独立线程来读取输出流来避免死锁。因此我们将这样来做:
使用线程来重定向就不会死锁
' GOOD CODE: 这里不会发生死锁.
Using p As New Diagnostics.Process
p.StartInfo.FileName = "sort"
p.StartInfo.UseShellExecute = False
p.StartInfo.RedirectStandardOutput = True
p.StartInfo.RedirectStandardInput = True
p.Start()
Dim op = ""
' do NOT WaitForExit yet since that would introduce deadlocks.
p.InputAndOutputToEnd("world" & vbCrLf & "hello", op, Nothing)
p.WaitForExit()
p.Close()
Console.WriteLine("OUTPUT:") : Console.WriteLine(op)
End Using
''' <summary>
''' InputAndOutputToEnd: a handy way to use redirected input/output/error on a p.
''' </summary>
''' <param name="p">The p to redirect. Must have UseShellExecute set to false.</param>
''' <param name="StandardInput">This string will be sent as input to the p. (must be Nothing if not StartInfo.RedirectStandardInput)</param>
''' <param name="StandardOutput">The p's output will be collected in this ByRef string. (must be Nothing if not StartInfo.RedirectStandardOutput)</param>
''' <param name="StandardError">The p's error will be collected in this ByRef string. (must be Nothing if not StartInfo.RedirectStandardError)</param>
''' <remarks>This function solves the deadlock problem mentioned at http://msdn.microsoft.com/en-us/library/system.diagnostics.p.standardoutput.aspx</remarks>
<Runtime.CompilerServices.Extension()> Sub InputAndOutputToEnd(ByVal p As Diagnostics.Process, ByVal StandardInput As String, ByRef StandardOutput As String, ByRef StandardError As String)
If p Is Nothing Then Throw New ArgumentException("p must be non-null")
' Assume p has started. Alas there's no way to check.
If p.StartInfo.UseShellExecute Then Throw New ArgumentException("Set StartInfo.UseShellExecute to false")
If (p.StartInfo.RedirectStandardInput <> (StandardInput IsNot Nothing)) Then Throw New ArgumentException("Provide a non-null Input only when StartInfo.RedirectStandardInput")
If (p.StartInfo.RedirectStandardOutput <> (StandardOutput IsNot Nothing)) Then Throw New ArgumentException("Provide a non-null Output only when StartInfo.RedirectStandardOutput")
If (p.StartInfo.RedirectStandardError <> (StandardError IsNot Nothing)) Then Throw New ArgumentException("Provide a non-null Error only when StartInfo.RedirectStandardError")
Dim outputData As New InputAndOutputToEndData
Dim errorData As New InputAndOutputToEndData
If p.StartInfo.RedirectStandardOutput Then
outputData.Stream = p.StandardOutput
outputData.Thread = New Threading.Thread(AddressOf InputAndOutputToEndProc)
outputData.Thread.Start(outputData)
End If
If p.StartInfo.RedirectStandardError Then
errorData.Stream = p.StandardError
errorData.Thread = New Threading.Thread(AddressOf InputAndOutputToEndProc)
errorData.Thread.Start(errorData)
End If
If p.StartInfo.RedirectStandardInput Then
p.StandardInput.Write(StandardInput)
p.StandardInput.Close()
End If
'
If p.StartInfo.RedirectStandardOutput Then outputData.Thread.Join() : StandardOutput = outputData.Output
If p.StartInfo.RedirectStandardError Then errorData.Thread.Join() : StandardError = errorData.Output
If outputData.Exception IsNot Nothing Then Throw outputData.Exception
If errorData.Exception IsNot Nothing Then Throw errorData.Exception
End Sub
Private Class InputAndOutputToEndData
Public Thread As Threading.Thread
Public Stream As IO.StreamReader
Public Output As String
Public Exception As Exception
End Class
Private Sub InputAndOutputToEndProc(ByVal data_ As Object)
Dim data = DirectCast(data_, InputAndOutputToEndData)
Try : data.Output = data.Stream.ReadToEnd : Catch e As Exception : data.Exception = e : End Try
End Sub
我偶尔会觉得有必要把COM的互操作和PInvoke结合起来。在某些场景中,用PInvoke的声明和方法会更容易编码一些。在这些场景中包括进COM对象,并且在签名上加上合适的Marshal标记也是合法的。
最简单的完成这些场景的方法是有本地的签名只暴露IUnkown实例。在托管代码这边,用一个对象声明并且标记上MarshalAs(UnmanagedType.IUnknown)。例如:
[DllImport("SomeDll.dll")]
[return: MarshalAs(UnmanagedType.IUnknown)]
public static extern object GetSomeComObject();
有一条需要记住,在这种场景中怎么处理ref这个关键字。在任何情况下,如果一个COM对象被当作来自于PInvoke的签名,CLR会假定他会去调用IUKnown::Release()。 相对的本地代码必须考虑这种情况,适当的对这个对象AddRef()。
这已经包括了任何的场景,像上面的代码,COM对象返回的值是function [1].
[原文作者]:Lisa Feigenbaum
[原文链接]:Walkthrough: Quick Search for Files and Symbols in Visual Studio 2010
快速查询是2010CTP(Community Technical Preview,社区技术预览)中我喜欢的功能之一,基本上我每时每刻都在使用。我希望读者在开发中也感受到这项功能的魅力。由于开发者需要不时在代码中寻找某些信息的需求,快速查询已经成为当今开发环境最常用的功能,这篇文章深入介绍了VS2010的这项功能。
此博文是October VS2010 CTP(VS2010社区技术预览十月版)功能预览系列的一部分,该系列旨在深入浅出地介绍VS2010及.NET 4.0中的新功能,即使读者没有下载社区技术预览,也可以在本文结尾处或以下论坛:
http://social.msdn.microsoft.com/Forums/en-US/vs2010ctpvbcs/thread/1eb74a01-0d50-4a58-b9b3-cdfae5807ef8
就本文内容及预览发表评论。
谢谢!
Lisa(非译者)
功能预览:针对文件及标签的快速查询
本文介绍的是VS2010中针对文件及标签的快速查询。快速查询指借助模糊查询技术协助开发者在代码中快速定位至某段代码。当打开项目工程中任意代码文件时,用户可以借助按下CTRL+,(CTRL键和逗号键)快速启动此功能。在快速查询窗口中,用户可以输入任意数量的查询条件,VS会根据输入值寻找项目中符合条件的标签,包括文件名,类型名,成员名。
此功能预览使用VS中自带的PeopleTrax例子来进行示范,用户可以在任意工程中使用此功能。
打开示例工程
- 示例文件位于:C:\Program Files\Visual Studio 10.0\Samples\1033\TeamDev Samples.zip.
- 解压缩示例文件压缩包到自定义路径,找到并打开PeopleTrax 文件夹。
- 双击 PeopleTrax.sln在 Visual Studio 2010中打开该工程.
使用快速查询来寻找标签
1. Open any file. Click inside the code editor and press CTRL+,. The following window appears. 打开任意文件,点击代码编辑器内部然后按下CTRL+,,将打开如下窗口
2. 在快速查询窗口顶部文本输入框内输入get. 快速查询窗口将列出所有包含单词 "get" 的标签, 如下图所示,输入不分大小写.
3. 在 "get"之后, 输入空格及单词 name.快速查询窗口会显示所有包含单词"get" 和 "name"的标签, 如下图所示.
4.按下向下键选中结果列表中的 GetNames 项. 按回车, Visual Studio 将定位到GetNames 方法定义处.
[原文作者]:Bill Horst
[原文链接]:Did you know? You can unwind the call stack from exceptions (Bill Horst)
解退一个异常堆栈的能力是Visual Basic.NET 2005的一个新引进的特性。当调式器触发了一个异常,你可以解退这个堆栈以便于使用代码编辑器修复这个异常并继续调式 修改后的代码。这个异常辅助用户界面有一个“Enable Editing”选项,这个选项可以在当前Solution中展开调式器到代码堆栈的最顶端。
当一个异常未被处理的时候,unwind将会自动发生,但是这个特性可以在Options dialog(在Tools下面)被开启或关闭。(见下图)
如果你试图在一个异常被触法后去编辑代码,并在解退栈之前,你可以被允许去使用“解退栈并且编辑代码”,“终止代码调试的session”,或“取消编辑”这三个选项。(见下图)
用户还可以从call stack窗口中进行解退栈,通过在需要的Call stack中单击右键并选择“Unwind To This Frame”。这个特性只是当异常已经被处发并且没有被解退栈时才有效,并且只有当在堆栈的足够靠前的的地方才有效。(见下图)
我们希望这个特性可以给您提供更有效率的代码调试,并可以帮助您加强在Visual Basic中使用“Edit and Continue”能力。
[原文作者]:Jared Parsons
[原文链接]:VB Catch ... When: Why so special?
VB Catch 的语法有一个独特的特点:When。它允许用户通过表达式筛选一些情况,而不是仅仅是筛选它的类型。 任何的代码都可以添加When去决定是否要处理某个异常。
Sub Sub1()
Try
DoSomeAction()
Catch ex As Exception When Filter(ex)
Stop
End Try
End Sub
新闻组通常会问,“为什么这么特别呢?”我也可以通过C#做同样的处理,例如:
static void Sub1()
{ try
{ DoSomeAction();
}
catch (Exception ex)
{ if (Filter(ex))
{ throw;
}
HandleException();
}
}
在某种程度上来说,确实是这样的。两种情况下代码都通过调用筛选程序来做决定是否要处理这个异常。细微的不同是在调用筛选的时候。
在VB中,When语句是作为IL异常来执行的。当异常被抛出,异常的筛选程序在堆栈展开之前就在进行了。这表示如果筛选方法创建了包括当时的堆栈的错误报告,它就可以展示出异常在什么地方发生的。
例如,在上面的代码中,如果DoSomeAction()被抛出,堆栈在筛选程序的表达式里面被检查,下面的堆栈就会被展示出来。
注意,怎么使得DosomeAction这个方法如此清晰可见的?这对错误报告和调查都是很有力的帮助。它也允许在真正出错的语句上设置断点,而不仅仅只是一个验尸报告。
在C#里面执行的代码是发生在堆栈展开之后。只要你不是在执行最优化的代码,仍然可以通过堆栈得到异常的原代码。但是你就不能在错误出现时检查代码了。
Visual Basic 9.0 提供了一个新的功能XML Literals,它将使得对XML的编程更加简单方便,并且在很大程度上的减少了我们的代码量。实际上,XML Literals使得我们用Visual Basic去做XML变得如此简单,以至于很多C#开发者也开始趋向于在VB.NET中来进行XML相关工作!如果想要了解更多信息,请参看下面来自TechEd US的内容:
[原文作者]:Jonathan Aneja
之前我们了解到Visual Studio 2008中包含一个程序集System.Data.DataSetExtensions.dll,里面定义了一个扩展方法AsEnumerable()。形式如下:
<Extension()> _
Public Function AsEnumerable(source As DataTable) As EnumerableRowCollection(Of DataRow)
这个函数接受一个DataTable的参数,返回一个实现IEnumerable的对象,从而可以使用LINQ的标准查询操作。你要做的就是导入程序集System.Data(工程模板默认已经做了),然后就可以调用AsEnumerable()来使用针对Dataset的LINQ
Dim customers = TestDS.Tables("Customers")
Dim franceCustomers = From cust In customers.AsEnumerable() _
Where cust!Country = "France" _
Select cust
现在有一个有趣的事是在VB中LINQ的工作方法,不需要在代码中明确地调用AsEnumerable()来实现编译通过。其实是这样的,尽管Datatable没有实现IEnumerable接口,然而编译器通过搜寻特定的方法来帮助实现,该方法能够使Datatable转变为某种可查询的类型。当VB编译器遇到基于某个类型的LINQ没有实现IEnumerable, IEnumerable(Of T), IQueryable, 或者IQueryable(Of T),它将按顺序做下面一些事:
1. 看类型中是否有一个一致的查询方法可见。
2. 看类型中是否有一个名字为AsQueryable的实例方法,改方法返回一个可查询的类型。
3. 看类型中是否有一个名字包含AsQueryable的扩展方法,改方法返回一个可查询的类型。
4. 看类型中是否有一个名字为AsEnumerable的实例方法,改方法返回一个可查询的类型。
5. 看类型中是否有一个名字包含AsEnumerable的扩展方法,改方法返回一个可查询的类型。
其中任何一步,如果编译器发现一个匹配的方法,将插入一个对它的调用。对于Datatable,当命名空间System.Data已经导入时,编译器在第五步为其发现了一个匹配方法,于是建立了一个对AsEnumerable的调用。结果你就能够写如101 LINQ Samples中LINQ to Dataset的代码了:
Dim customers = TestDS.Tables("Customers")
Dim franceCustomers = From cust In customers _
Where cust!Country = "France" _
Select cust
实际上就是要你提供一个AsEnumerable的扩展方法,该方法返回一个可查询的类型,从而使LINQ起作用。
注意对于强类型的Datasets,你不需要调用AsEnumerable,因为它们继承自TypedTableBase(Of T),该类型实现了IEnumerable,它是VS2008中的一个新类型,在VS2005中Dataset设计器将生成继承自DataTable的代码,并且显式地实现IEnumerable。
[原文链接]:Reflection on COM objects
我希望拥有一台这样的“完全形态”照样机, 当你拍摄一个物体(object)时,它不仅仅把二维图像存储在SD卡上,而是把物体记录在“完全形态”这个“存储卡”上,并且了解这个物体和其所构成的所有关系。这样会包括这个物体各个角度的三维图像,关于其历史意义的短文,对其文化和经济中所扮演的角色的描述,详尽的内部图表展现了物体是怎样工作,以及一系列的超链接指向与这个物体有关的主体–并且所有的这些都会被保存在维基百科中。
你准备如何创建这样一个相机呢?
以上这些是为了引出反射这个主题…
.NET 对象上的反射通过 System.Type 完成,非常简单。比如"Dim type = GetType(System.string)",现在您可以查看所有成员和 System.String 类的继承层次结构。
如果我们有一个(.Net) interop 程序集,反射COM类型也同样简单。比如,一个project 添加COM引用Microsoft Speech Library,然后可以进行反射做”GetType(SpeechLib.SpVoice)”。其实这样是对(.Net)interop程序集中“Runtime Callable Wrapper”的反射,“Runtime Callable Wrapper”是从COM类型的类库得到的,包括了这个COM类库所有的信息。[译注:Runtime Callble Wrapper(RCW),我们可以生成一个RCW,通过RCW,.Net用户就可以使用.Net对象而不是COM组件,为了实现传统的COM程序与.NET程序之间的相互调用,.NET提供了包装类RCW(Runtime Callable Wrapper)和CCW(COM Callable Wrapper)。每当一个.NET客户程序调用一个COM对象的方法时就会创建一个RCW对象,每当一个COM客户程序调用一个.NET对象的方法时就会创建一个CCW对象。
不过有时,你只有COM组件而没有.Net interop程序集。在我为Visual Studio写托管插件时就遇过这样的情况。对于此处反射必须使用ITypeInfo而不是 System.Type。 以下是代码以获取该 ITypeInfo、然后输出所有成员。我是在 COM 编程的初学者,欢迎所有的建议和改进。(注意:我特意不尝试创建包装 ITypeInfo / TYPEDESC 的 API,虽然那样是比较成熟的) [译注:一般情况下,.Net调用COM组件,我们都会让Visual Studio生成Interop 程序集,这样依然可以用一般的反射,但是让vs.net自动生成一个包装过的.net类库。这种方法虽然方便,但是有很明显的缺点,最致命的就是开发的机器上安装的Com对象的版本比客户机器上安装的高,开发的程序无法正确的运行。
' REFLECTION ON COM OBJECTS. Lucian Wischik, October 2008.
' (with thanks to Eric Lippert and Sonja Keserovic for their help)
'
' CLR允许你通过GetType()进行反射类型
' 对于COM组件,有时你需要通过ITypeInfo/TYPEDESC来进行反射
' * 如果COM组件已经被转换成一个托管的RCW
' 这时可以用RCW进行反射
' * 如果没有RCW可用,还是需要通过ITypeInfo/TYPEDESC
' ItypeInfo是指向COM组件的指针,可以和System.Type得到一样的信息,Visual Studio对于COM的智能化提示,正是使用这个来反射COM组件
' * 如果没有类库,我们对组件不能做反射
'
' ITypeInfo – 对class/interface/structure的引用
' TYPEDESC – 表示一些原型(比如,Integer),或者一些复合类型
' 下面显示了怎么使用ItypeInfo来进行反射…
'
Option Strict On
Imports System.Runtime.InteropServices
Module Module1
''' <summary>
''' UnmanagedCreateCOM: this is an unmanaged function which calls CoCreateInstance
''' to create an instance of CLSID_WebBrowser.
''' </summary>
''' <returns>returns a new COM object. The caller is expected to AddRef on it.</returns>
<DllImport("createcom.dll", SetLastError:=False)> _
Function UnmanagedCreateCOM() As IntPtr
End Function
Sub Main()
' 对.net类型的反射最直接:
Console.WriteLine("=== REFLECTION ON .NET TYPE VIA .NET REFLECTION ===")
ReflectOnDotNetType(GetType(System.String))
' 如果将COM组件加到引用中,反射也是很简单的
' 我们将一个COM组件加到引用中,然后反射
' 和普通的.net类型一样使用反射:
Console.WriteLine("=== REFLECTION ON RCW'D COM TYPE VIA .NET REFLECTION ===")
ReflectOnDotNetType(GetType(SpeechLib.SpVoice))
' But .net reflection gives pointless results on COM objects which lack an interop assembly:
' GetObjectForIUnknown just creates a tiny stub RCW for them with a handful of common functions.
Console.WriteLine("=== REFLECTION ON NON-RCW'D COM TYPE VIA ITYPEINFO REFLECTION ===")
ReflectOnDotNetType(Marshal.GetObjectForIUnknown(UnmanagedCreateCOM()).GetType())
' 这样我们需要使用ITypeInfo来代替:
Console.WriteLine("=== REFLECTION ON NON-RCW'D COM TYPE VIA COM REFLECTION ===")
ReflectOnCOMObjectThroughITypeInfo(Marshal.GetObjectForIUnknown(UnmanagedCreateCOM()))
End Sub
''' <summary>
''' ReflectOnDotNetType: 反射.net 类型
''' </summary>
''' <param name="tt">the type to reflect upon</param>
Sub ReflectOnDotNetType(ByVal tt As System.Type)
Dim qt As New Queue(Of System.Type)
qt.Enqueue(tt)
While qt.Count > 0
Dim t = qt.Dequeue
Console.WriteLine("TYPE {0}", t.ToString)
For Each i In t.GetInterfaces
Console.WriteLine(" inherits {0}", i.ToString)
qt.Enqueue(i)
Next
For Each m In t.GetMembers
Console.WriteLine(" member {0}", m.ToString)
Next
End While
End Sub
''' <summary>
''' IDispatch: 托管Idispatch 接口
''' </summary>
''' <remarks>We don't use GetIDsOfNames or Invoke, and so haven't bothered with correct signatures for them.</remarks>
<ComImport(), Guid("00020400-0000-0000-c000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)> _
Interface IDispatch
Sub GetTypeInfoCount(ByRef pctinfo As UInteger)
Sub GetTypeInfo(ByVal itinfo As UInteger, ByVal lcid As UInteger, ByRef pptinfo As IntPtr)
Sub GetIDsOfNames_unused()
Sub Invoke_unused()
End Interface
''' <summary>
''' ReflectOnCOMObjectThroughITypeInfo: 一个支持Idispatch and attempts 的COM组件
''' 得到它的ItypeInfor 接口
''' 通过这个方法反射COM类型.
''' </summary>
''' <param name="com">the com object upon which to reflect</param>
Sub ReflectOnCOMObjectThroughITypeInfo(ByVal com As Object)
' How do we get ITypeInfo for a COM object?
' It would be nice to use Marshal.GetITypeInfoForType. But that fails when the com object
' doesn't have an interop assembly (e.g. when the com object was created for us
' by native code). So instead we have to use IDispatch::GetTypeInfo.
Dim idisp = CType(com, IDispatch)
Dim count As UInteger = 0 : idisp.GetTypeInfoCount(count)
If (count < 1) Then Throw New ArgumentException("No type info", "com")
Dim _typeinfo As IntPtr : idisp.GetTypeInfo(0, 0, _typeinfo)
If (_typeinfo = IntPtr.Zero) Then Throw New ArgumentException("No ITypeInfo", "com")
Dim typeInfo = CType(Marshal.GetTypedObjectForIUnknown(_typeinfo, GetType(ComTypes.ITypeInfo)), ComTypes.ITypeInfo)
Marshal.Release(_typeinfo) ' to release the AddRef that GetTypeInfo did for us.
AddTypeInfoToDump(typeInfo)
While typeInfosToDump.Count > 0
DumpTypeInfo(typeInfosToDump.Dequeue())
End While
End Sub
''' <summary>
''' DumpType: prints information about an ITypeInfo type to the console -- name, inheritance, members
''' </summary>
''' <param name="typeInfo">the type to dump</param>
Sub DumpTypeInfo(ByVal typeInfo As ComTypes.ITypeInfo)
' Name:
Dim typeName = "" : typeInfo.GetDocumentation(-1, typeName, "", 0, "")
Console.WriteLine("TYPE {0}", typeName)
' TypeAttr: contains general information about the type
Dim pTypeAttr As IntPtr : typeInfo.GetTypeAttr(pTypeAttr)
Dim typeAttr = CType(Marshal.PtrToStructure(pTypeAttr, GetType(ComTypes.TYPEATTR)), ComTypes.TYPEATTR)
' Inheritance:
For iImplType = 0 To typeAttr.cImplTypes - 1
Dim href As Integer : typeInfo.GetRefTypeOfImplType(iImplType, href)
' "href" is an index into the list of type descriptions within the type library.
Dim implTypeInfo As ComTypes.ITypeInfo = Nothing : typeInfo.GetRefTypeInfo(href, implTypeInfo)
' And GetRefTypeInfo looks up the index to get an ITypeInfo for it.
Dim implTypeName = "" : implTypeInfo.GetDocumentation(-1, implTypeName, "", 0, "")
Console.WriteLine(" Implements {0}", implTypeName)
AddTypeInfoToDump(implTypeInfo)
Next
' Function/Sub/Property成员:
' Note that property accessors are flattened, e.g. for a property "Fred as Integer"
' it will be represented as two members "[Get] Function Fred() As Integer", and "[Put] Sub Fred(Integer)"
' Each member is uniquely identified by an integer "MEMID".
' This memid is what's used e.g. when invoking the member.
For iFunc = 0 To typeAttr.cFuncs - 1
' FUNCDESC 是这里的主要结构:
Dim pFuncDesc As IntPtr : typeInfo.GetFuncDesc(iFunc, pFuncDesc)
Dim funcDesc = CType(Marshal.PtrToStructure(pFuncDesc, GetType(ComTypes.FUNCDESC)), ComTypes.FUNCDESC)
' Each function notionally has a list of names associated with it. I'll just pick the first.
Dim names As String() = {""}
typeInfo.GetNames(funcDesc.memid, names, 1, 0)
Dim funcName = names(0)
' Function 参数:
Dim cParams = funcDesc.cParams
Dim s = ""
For iParam = 0 To cParams - 1
Dim elemDesc = CType(Marshal.PtrToStructure(New IntPtr(funcDesc.lprgelemdescParam.ToInt64 + Marshal.SizeOf(GetType(ComTypes.ELEMDESC)) * iParam), GetType(ComTypes.ELEMDESC)), ComTypes.ELEMDESC)
If s.Length > 0 Then s &= ", "
If (elemDesc.desc.paramdesc.wParamFlags And 2) <> 0 Then s &= "out "
s &= DumpTypeDesc(elemDesc.tdesc, typeInfo)
Next
' 输出函数的其他信息:
Dim props = ""
If (funcDesc.invkind And ComTypes.INVOKEKIND.INVOKE_PROPERTYGET) <> 0 Then props &= "Get "
If (funcDesc.invkind And ComTypes.INVOKEKIND.INVOKE_PROPERTYPUT) <> 0 Then props &= "Set "
If (funcDesc.invkind And ComTypes.INVOKEKIND.INVOKE_PROPERTYPUTREF) <> 0 Then props &= "Set "
Dim isSub = (funcDesc.elemdescFunc.tdesc.vt = VarEnum.VT_VOID)
s = props & If(isSub, "Sub ", "Function ") & funcName & "(" & s & ")"
s &= If(isSub, "", " as " & DumpTypeDesc(funcDesc.elemdescFunc.tdesc, typeInfo))
Console.WriteLine(" " & s)
typeInfo.ReleaseFuncDesc(pFuncDesc)
Next
' Field 成员:
For iVar = 0 To typeAttr.cVars - 1
Dim pVarDesc As IntPtr : typeInfo.GetVarDesc(iVar, pVarDesc)
Dim varDesc = CType(Marshal.PtrToStructure(pVarDesc, GetType(ComTypes.VARDESC)), ComTypes.VARDESC)
Dim names As String() = {""}
typeInfo.GetNames(varDesc.memid, names, 1, 0)
Dim varName = names(0)
Console.WriteLine(" Dim {0} As {1}", varName, DumpTypeDesc(varDesc.elemdescVar.tdesc, typeInfo))
Next
Console.WriteLine()
End Sub
''' <summary>
''' DumpTypeDesc: given a TYPEDESC, dumps it out into a string e.g. "Ref Int" or
''' "Array of MyTypeInfo". Also calls AddTypeInfoToDump for every ITypeInfo encountered.
''' </summary>
''' <param name="tdesc">the TYPEDESC to dump</param>
''' <param name="context">the ITypeInfo that contained this TYPEDESC, for context</param>
''' <returns>a string representation of the TYPEDESC</returns>
Function DumpTypeDesc(ByVal tdesc As ComTypes.TYPEDESC, ByVal context As ComTypes.ITypeInfo) As String
Dim vt = CType(tdesc.vt, VarEnum)
Select Case vt
Case VarEnum.VT_PTR
Dim tdesc2 = CType(Marshal.PtrToStructure(tdesc.lpValue, GetType(ComTypes.TYPEDESC)), ComTypes.TYPEDESC)
Return "Ref " & DumpTypeDesc(tdesc2, context)
Case VarEnum.VT_USERDEFINED
Dim href = tdesc.lpValue.ToInt32()
Dim refTypeInfo As ComTypes.ITypeInfo = Nothing : context.GetRefTypeInfo(href, refTypeInfo)
AddTypeInfoToDump(refTypeInfo)
Dim refTypeName = "" : refTypeInfo.GetDocumentation(-1, refTypeName, "", 0, "")
Return refTypeName
Case VarEnum.VT_CARRAY
Dim tdesc2 = CType(Marshal.PtrToStructure(tdesc.lpValue, GetType(ComTypes.TYPEDESC)), ComTypes.TYPEDESC)
Return "Array of " & DumpTypeDesc(tdesc2, context)
' lpValue is actually an ARRAYDESC structure, which also has information on the array dimensions,
' but alas .Net doesn't predefine ARRAYDESC.
Case VarEnum.VT_VOID ' e.g. IUnknown::QueryInterface(Ref GUID, out Ref Ref Void)
Return "Void"
Case VarEnum.VT_VARIANT
Return "Object"
Case VarEnum.VT_UNKNOWN
Return "IUnknown*"
Case VarEnum.VT_BSTR
Return "String"
Case VarEnum.VT_LPWSTR
Return "wchar*"
Case VarEnum.VT_LPSTR
Return "char*"
Case VarEnum.VT_HRESULT
Return "HResult"
Case VarEnum.VT_BOOL
Return "Bool"
Case VarEnum.VT_I1
Return "SByte"
Case VarEnum.VT_UI1
Return "Byte"
Case VarEnum.VT_I2
Return "Short"
Case VarEnum.VT_UI2
Return "UShort"
Case VarEnum.VT_I4, VarEnum.VT_INT
Return "Integer"
Case VarEnum.VT_UI4, VarEnum.VT_UINT
Return "UInteger"
Case VarEnum.VT_I8
Return "Long"
Case VarEnum.VT_UI8
Return "ULong"
Case Else
' 这里还有其他类型,我没有在这里列出
' 大家可以根据需要将其他的列出来.
Return vt.ToString()
End Select
End Function
Dim typeInfosToDump As New Queue(Of ComTypes.ITypeInfo)
Dim typeInfosDumped As New HashSet(Of String)
'
Sub AddTypeInfoToDump(ByVal typeInfo As ComTypes.ITypeInfo)
Dim typeName = "" : typeInfo.GetDocumentation(-1, typeName, "", 0, "")
If typeInfosDumped.Contains(typeName) Then Return
typeInfosToDump.Enqueue(typeInfo)
typeInfosDumped.Add(typeName)
End Sub
EndModule
[原文链接]:LiveRun - a VS plugin to see the output of your program immediately
假设你正在会议室里演示即时编译的程序。有什么最佳的方式来进行演示呢?
你还在往代码窗口里输入代码?这样,你得依赖听众的想像力――他们在脑海中构造这个程序是怎样运行的。此外,你还得依赖他们相信你的代码真的像和你所说的一样运作。
或者你要不停的运行你的代码,这样程序的输出窗口会弹出,然后听众可以看到代码的实际工作情况?这是有风险的,因为每次切换代码和输出窗口会中断流程。并且你得依赖听众会记得他们每次看到的输出信息所对应的代码。
我想这是一个可以被技术所解决的问题!我为Visual Studio 2008 写了一个小插件。它关注的是当前文本缓冲中所包含的代码,并在后台编译它,然后在前端窗口中显示输出。这一系列动作大概每2秒钟重复一次。你甚至不需要保存再重新编译代码就能看到输出了。这个插件特意制做成独立的控制台程序因为它用不着输入。这是一幅截图:

源代码很小而且简单易懂,并且可以从上面的链接中获得。
有两个关键的地方。第一是要使用多线程进行处理。我想让源代码在后台线程中编译因此它并不会影响Visual Studio UI. 但是要获取当前缓冲的文本你必须使用UI线程,而且要显示输出你也得使用它。我使用System.Timers.Timer在后台线程触发它的事件,同时为需要UI线程的任务调用form.Invoke(…)。
我还使用了“non-AutoReset”计时器。我让它获取源码并进行编译+运行+显示,暂停2秒后,再去获取源码并进行编译+运行+显示,如此循环。换句话说,计时器的时间间隔必须设为2 秒在处理完先前的定时事件之后。
''' <summary>
''' OnTimer 处理non-autoreset计时器信号。它运行在后台线程中。从当前的缓冲区中获取
''' 源代码并编译它,然后显示输出。
''' </summary>
''' <remarks></remarks>
Sub OnTimer() Handles t.Elapsed
Try
Dim oldsrc = src
'我们在后台线程中。但只能从 UI 线程获取源...
'此委托将获取源代码并将它存储在"src"字段中
f.Invoke(New Action(AddressOf GetSource))
If src <> oldsrc Then
Dim oldoutput = output
' 在后台编译并运行
output = CompileAndRun(src)
If output <> "" OrElse oldoutput = "" Then
' 在屏幕上显示输出必须在UI线程中完成
' 此委托从"output"字段中得到要输出的内容并显示它
f.Invoke(New Action(AddressOf ShowOutput))
End If
End If
Finally
t.Start()
End Try
End Sub
另一个关键时刻与如何运行代码并捕获它的输出有关。在“My”命名空间中,VB在这方面有很多有用的函数。我的主要问题是合理的处理异常不留任何遗漏。(注意:获取临时文件名的代码并不完全准确: 事实上你在某个状态之前得到一个未使用的临时文件名并不意味着这个文件名会一直不被使用;同样,它也并不意味着添加了“.vb”扩展名的临时文件名会一直不被使用。但是把这种情况处理的更准确看直来并不十分划算;不管怎样,在异常处理中我们将所有的问题恢复到正常状态。)
Function CompileAndRun(ByVal src As String) As String
Dim fn_exe = ""
Dim fn_src = ""
Dim vbc As System.Diagnostics.Process = Nothing
Dim exe As System.Diagnostics.Process = Nothing
Try
' 准备编译
fn_src = My.Computer.FileSystem.GetTempFileName() & ".vb"
My.Computer.FileSystem.WriteAllText(fn_src, src, False)
fn_exe = My.Computer.FileSystem.GetTempFileName() & ".exe"
Dim framework = Environment.ExpandEnvironmentVariables("%windir%\Microsoft.Net\Framework")
Dim latest_framework = (From d In My.Computer.FileSystem.GetDirectories(framework) Where d Like "*\v*" Select d).Last
' 编译
vbc = System.Diagnostics.Process.Start(New ProcessStartInfo _
With {.CreateNoWindow = True, _
.UseShellExecute = False, _
.FileName = latest_framework & "\vbc.exe", _
.Arguments = String.Format("/out:""{0}"" /target:exe ""{1}""", fn_exe, fn_src)})
Dim vbc_done = vbc.WaitForExit(3000)
If Not vbc_done Then Return ""
If vbc.ExitCode <> 0 Then Return ""
' 运行
Dim pinfo = New ProcessStartInfo With {.CreateNoWindow = True, _
.UseShellExecute = False, _
.FileName = fn_exe, _
.RedirectStandardOutput = True}
exe = New System.Diagnostics.Process With {.StartInfo = pinfo}
exe.Start()
Dim output = exe.StandardOutput.ReadToEnd
Dim exe_done = exe.WaitForExit(3000)
If Not exe_done Then Return ""
Return output
Finally
' 我们可以巧妙的关闭VBC进程
If vbc IsNot Nothing Then
If Not vbc.HasExited Then
Try : vbc.Kill() : Catch ex As Exception : End Try
Try : vbc.WaitForExit() : Catch ex As Exception : End Try
End If
Try : vbc.Close() : Catch ex As Exception : End Try
vbc = Nothing
End If
' 我们可以巧妙的关闭EXE
If exe IsNot Nothing Then
If Not exe.HasExited Then
Try : exe.Kill() : Catch ex As Exception : End Try
Try : exe.WaitForExit() : Catch ex As Exception : End Try
End If
Try : exe.Close() : Catch ex As Exception : End Try
exe = Nothing
End If
' 删除剩余的文件
Try : My.Computer.FileSystem.DeleteFile(fn_exe) : Catch ex As Exception : End Try
Try : My.Computer.FileSystem.DeleteFile(fn_src) : Catch ex As Exception : End Try
End Try
End Function
一直以来,我都很喜欢听到建议,bug 修复,代码改进以及评论!
很多人问我TableAdapter是否能够从存储过程里读取多组数据结果。最直接的回答是:不能。你不能通过TableAdapter.Fill()方法来得到一个Dataset。但是我们可以通过另一种简单的方法来实现。
DataAdapter.Fill()和多组数据结果
TableAdapter.Fill()方法通过调用DataAdapter.Fill()从数据库中读取数据。DataSet.Fill() 方法可以从存储过程里读取多组数据结果。为了获得多组数据结果,可以应用DataAdapter.Fill()的一个重载方法,它将Dataset作为参数,这样就可以把存储过程的多组数据结果返回给包含有多个表的Dataset。
这里,我们通过一个简单的示例来演示一下这种方法是怎样实现的:
假设在Northwind数据库里有一个存储过程dbo.spSelectCustomersOrders,
CREATE PROCEDURE spSelectCustomersOrders
AS
BEGIN
SET NOCOUNT ON;
SELECT * FROM Customers
SELECT * FROM Orders
END
GO
下面的代码调用了这个存储过程,并且把2组数据结果存储在Dataset里。
Dim myConn As New System.Data.SqlClient.SqlConnection
Dim myAdapter As New System.Data.SqlClient.SqlDataAdapter
Dim mySelectCommand As New System.Data.SqlClient.SqlCommand
Dim myDataset As New System.Data.DataSet
myConn.ConnectionString = "Data Source=.\SQLExpress;Initial Catalog=Northwind;Integrated Security=True"
mySelectCommand.Connection = myConn
mySelectCommand.CommandText = "dbo.spSelectCustomersOrders"
myAdapter.SelectCommand = mySelectCommand
myAdapter.Fill(myDataset)
For Each table As System.Data.DataTable In myDataset.Tables
Console.WriteLine("Table Name:" & table.TableName)
Next
代码的输出形式如下:
Table Name: Table
Table Name: Table1
我们可以看到,DataAdapter.Fill()方法执行了存储过程,并且把2组数据结果分别存储在2个数据表里。
TableAdapter的解决方案
然而,为什么TableAdapter.Fill()方法不能够正确地处理多组数据结果?那是因为TableAdapter.Fill()调用的DataAdapter.Fill()方法是以DataTable作为参数,而不是Dataset。这种情况,我们只需要在TableAdapter里创建一个新的Fill方法,令其调用以Dataset为参数的DataAdapter.Fill()方法。
假设这里有一个包含Customers和Orders的NorthwindDataset.xsd文件。让我们用上面的存储过程来实现新的Fill方法。把下面的代码加到partial class文件里。(在Dataset Designer上,可以通过双击或者右键选择"View Code"来进入partial class,当然也可以手动创建一个空的class文件。)
Namespace NorthwindDataSetTableAdapters
Partial Public Class CustomersTableAdapter
Public Function FillCustomersOrders(ByVal dataSet As NorthwindDataSet) As Integer
Dim multiSelectCommand As New System.Data.SqlClient.SqlCommand
Dim returnValue As Integer
multiSelectCommand.Connection = Me.Connection
multiSelectCommand.CommandText = "dbo.spSelectCustomersOrders"
Me.Adapter.SelectCommand = multiSelectCommand
'' Map auto-created Table1 that holds the second result-set (Orders rows) to
'' Orders DataTable in our Dataset.
Me.Adapter.TableMappings.Add("Table1", "Orders")
returnValue = Me.Adapter.Fill(dataSet)
Return returnValue
End Function
End Class
End Namespace
有两点需要特别注意:
首先,新的FillCustomersOrders是以Dataset为参数,这样当我们调用DataAdapter.Fill()方法时,数据结果就会准确地存储到Dataset里。
第二,注意我们是怎样应用TableMapping将自动生成的数据表映射到Dataset里的Orders表。当应用DataAdapter.Fill()来读取多组数据结果,每一组数据结果都被单独地存储在Dataset的数据表里。默认情况下,这些数据表被命名为Table, Table1, Table2…,为了将这些数据标与Dataset里定义的数据表相对应,我们应用TableMapping。如果你打开NorthwindDataset.xsd后面的代码,在TableAdapter class的InitAdapter()方法,你就会看到类似的代码:
tableMapping.SourceTable = "Table"
tableMapping.DataSetTable = "Customers"
'' Colum mapping code skipped
...
Me._adapter.TableMappings.Add(tableMapping)
这段代码是为了保证DataAdapter.Fill方法返回的数据表与Dataset里的数据表相对应。在我们FillCustomersOrders示例里,第二组结果包含的是Orders信息,所以我们在Table1和Orders之间创建了映射关系,确保数据Fill到Orders表中。
把以上代码添加到partial class后,你就可以调用FillCustomersOrders方法来fill Customers和Orders。
CustomersTableAdapter.FillCustomersOrders(Me.NorthwindDataSet)
性能的考虑
有些情况下,这种方法的确很有效。但是这也要看情况,也许你会想到这个方法可以避免多次访问数据库,从而提高性能,但如果仅仅只需要获取一小部分数据,却应用这种方法一次读取了大量的数据,这同样也会降低性能,倒不如一次读取小部分数据,需要其它数据时,与数据库建立另一个连接,再读取。ADO.NET在处理多个数据库连接方面性能优化得还是不错的,很多情况下,都不至于导致性能瓶颈。总之我们只需要遵循最基本的原则:只在需要的时候,才去读取数据。
但有些情况下,读取多组数据结果还是很有帮助的,所以,应用我在这里所介绍的方法吧,但时刻也不要忘记性能的问题。
相关资源
这是我们如何在VB将来某一个版本中实现Co/Contra-Variance的一系列探索的第一项。 这并不是对下一代VB的承诺,而是作为一个提议放在这里,从而能够从我们潜在的用户那儿得到一些反馈。
Sub EatFruit(ByVal x As IEnumerable(Of Fruit))
...
Dim x As New List(Of Apple)
x.Add(New GrannySmith)
x.Add(New GoldenDelicious)
EatFruit(x)
' ERROR: cannot convert List(Of Apple) to IEnumerable(Of Fruit)
观察一下以上的代码,或许你觉的它是没问题的。这是个很常见的情景:一个库函数处理一些data类型,但是你的自定义类型继承了这个data类型。如何能够将一个自定义类型的集合传给这个库函数呢?
我们正在考虑为VB语言增加一个特征来支持这种转变,我们称之为“Co/Contra-Variance”,简称为“Variance”。实际上大概在2005年的时候,CLR已经支持“Variance”了,但并没有一种发布的语言用到它。但是一些其它的语言用到它,这里有一些链接,都是 关于这个主题的:
我将谈一下VB中如何使用Variance:如何用它让你的代码更简单或清晰,如果我们实现了它能解决什么问题。Variance博大精深,apples转化为fruit只不过是它的寻常一功能而已,以上的文章更是让人觉得它复杂。但是我相信我们提议的语法和例子能够揭去这层神秘的面纱。
昨天我用Variance解决了一个问题:
Function Call(instance As Expression, method As MethodInfo, arguments As IEnumerable(Of Expression)) As MethodCallExpression
...
' Create a new callsite that takes two arguments:
Dim args As New List(Of ConstantExpression)
args.Add(Expression.Constant("x"))
args.Add(Expression.Constant("y"))
'
Dim call1 = Expression.Call(instance, method, args)
' args inherits from IEnumerable(Of ConstantExpression), which
' variance-converts to IEnumerable(Of Expression)
对应于第一段,我们转化为:
' some example classes to get us started
Class Food : End Class
Class Fruit : Inherits Food : End Class
Class Apple : Inherits Fruit : End Class
Class GrannySmith : Inherits Apple : End Class
Class GoldenDelicious : Inherits Apple : End Class
' GoldenDelicious < Apple < Fruit < Food
' using < in the mathematical sense of "is smaller than",
' and in the VB sense of "can be converted to"
Class AppleBasket
Implements IReadOnly(Of Apple)
Implements IWriteOnly(Of Apple)
End Class
“out”参数
我们想用关键字“out”和“in”来介绍Variance:
Interface IReadOnly(Of Out T)
Function Read() As T
End Interface
' "Out" declares that T will only ever be used
' as return type of functions *
Dim x As IReadOnly(Of Apple) = New AppleBasket
Dim y As IReadOnly(Of Fruit) = x
Dim f As Fruit = y.Read()
' This is guaranteed not to throw InvalidCastException
当接口的参数类型是“out”时,它表明这个类型只能用来返回(其他地方表明传出数据),如果试图调用“Sub(ByVal x As T)”,就会产生一个编译错误。(CLR如何使用Variance限制了很多设计,我们希望能和其他的.NET语言兼容。)
正是这个“out”保证了CLR能够转化接口:
' GoldenDelicions < Apple < Fruit < Food < Object
Dim apples As IReadOnly(Of Apple) = New AppleBasket
' It is allowed to change to an IReadOnly of something bigger:
Dim fruits As IReadOnly(Of Fruit) = apples
Dim foods As IReadOnly(Of Food) = apples
Dim things As IReadOnly(Of Object) = fruits
' It is an ERROR to change to an IReadOnly that is smaller:
Dim golds As IReadOnly(Of GoldenDelicious) = apples
' Also an ERROR to change to something unrelated
Dim cars As IReadOnly(Of Car) = apples
通常来说,如果你有一个泛型接口IreadOnly(Of Out T),然后你可以把“Of T”转换为它可以转化的其它类型。很显然,这是类型安全的:
Variance转换是类型安全的和有效的,它只用一句中间语言指令来实现,不需要运行时Runtime检查。(这区别于数组:每次往数组里放东西,都得进行Runtime检查。)
参数类型是“out”的接口被成为covariant。
“In”参数
Interface IWriteOnly(Of In T)
Sub Write(ByVal x As T)
End Interface
' "In" declares that T will only ever be used
' as ByVal arguments to functions.
Dim x As IWriteOnly(Of Apple) = New AppleBasket
Dim z As IWriteOnly(Of GoldenDelicious) = x
z.Write(New GoldenDelicious)
“In”参数正好相反。当接口的参数类型是“In”时,它表明这个类型只能用于ByVal引用(其他地方表明传入数据),如果试图调用“Function f() as T”,就会产生一个编译错误。
“In”参数保证了反向的类型转换:
' GoldenDelcious < Apple < Fruit < Food < Object
Dim apples As IWriteOnly(Of Apple) = New AppleBasket
' It is allowed to convert to an IWriteOnly of something smaller:
Dim golds As IWriteOnly(Of GoldenDelicious) = apples
' It is an ERROR to convert to something bigger, or unrelated:
Dim foods As IWriteOnly(Of Food) = apples
Dim cars As IWriteOnly(Of Car) = apples
参数类型是“out”的接口被成为contravariant。
同时有“In”和“Out”
直到20世纪90年代,人们仍然在为“In”或者“Out”是否是正确的而争论。现在我们知道了他们都是正确的!第一个在这方面有说服力的论据是1995年Giuseppe Castagna的研究论文"
Conflict Without A Cause"
[PDF]。
这里有两个例子,说明他们为什么是正确的,以及将他们放在一起:
Class AppleBasket
Implements IReadOnly(Of Apple)
Implements IWriteOnly(Of Apple)
Private m_value As Apple
Public Function Read() As Apple Implements IReadOnly(Of Apple).Read
Return m_value
End Function
Public Sub Write(ByVal x As Apple) Implements IWriteOnly(Of Apple).Write
m_value = x
End Sub
End Class
Pipes: 为内部和外部契约(contracts)用“In”和“Out”
' Here we implement a Pipe. Each element in the pipe is an ICollection.
' IList < ICollection < IEnumerable
'
' When we give out reader ("Out") access to the public, we force it so
' readers can only ever assume that elements are IEnumerable.
' And when we give out writer ("In") access, we force it so
' that writers must always put in IList
'
' This future-proofs our code in TWO directions: it forces the
' implementation to provide IList in case in the future we want
' to expose more to the clients; but it does so without making
' a public commitment to the clients that future implementations
' would have to uphold.
Class MyPipe(Of T)
Implements IWriteOnly(Of T)
Implements IReadOnly(Of T)
Private contents As New Stack(Of T)
Public Sub Write(ByVal x As T) Implements IWriteOnly(Of T).Write
contents.Push(x)
End Sub
Public Function Read() As T Implements IReadOnly(Of T).Read
Return contents.Pop()
End Function
End Class
我们很希望能得到用户的反馈,从而帮助我们决定是否将这个功能加入VB语言,并且思考让他如何工作。请踊跃评论。
在以后的几周里,我会新更多关于Variance的东西。
另外:关于这边文正的标题,这是我们的设想:
Dim x As New List(Of Apple)
Dim y As List(Of Fruit) = x
'
' ERROR: List(Of Fruit) cannot be converted to List(Of Apple)
' Consider using IEnumerable(Of Fruit) instead.
[原文作者]:Lisa Feigenbaum
Code snippets是在Visual Basic 2005中引入的。它提供了一个简单的办法来处理特殊的编码任务或者将一段代码在应用程序中不同的部分重复使用。Code snippets能通过许多种不同的方法来插入。具体哪种方法根据不同的情况而定。 当我们需要浏览code snippet的时候,可以用Code Snippet inserter.
Code Snippet Inserter

Code Snippet Insert 可以用两种方法触发:
· 在你的代码文件中输入?然后按Tab;
· 点右键,然后从出现的菜单中选择“Insert Snippet..”
然后你就能根据snippet的目录结构,选择到你需要使用的Snippet。 可以注意到title旁边有tooltips显示对当前选择的snippet的描述和快捷键的。

对那些你经常使用的或者需要快速获取的snippets,你可以使用snippets的快捷键。一旦你知道了snippet的快捷键,你就能在你的代码中通过键入快捷键+Tab来插入snippet. 要知道一个snippet的快捷键, 可以通过在code snippet inserter中选中一个snippet来查看或者直接在Code Snippets Manager中查看。
Code Snippets Manager
“快捷键+Tab”插入模式也有利于使用关键词来做为快捷键的情况。比如说,在你的代码中键入“Select+Tab”,你能看到如下的snippet被插入了。这种snippets能扩展。
An Expansion Snippet: Select Case
如果你按了tab键并且完成了你不想要的一个snippet的输入,只要按“CTRL+Z”就能使你的代码回到之前的状态。
对其他更多的面向任务的snippet,snippet快捷键以相应snippet路径的缩写开头。这样,因为snippet“Send an Email”在“connectivity”目录下,所以它的快捷键是“conEmail。注意:虽然大多数快捷键包含大写字母,为了能正确插入snippet,你输入的类型并不需要完全与相应的大小写类型匹配。 就是说,你输入“conemail”或者“CONEMAIL”都能使snippet正确插入。
通常你只需要记住一部分的snippet的快捷键。那样的话,你一旦键入快捷键的开头部分,然后按?+Tab,一个完整的快捷键列表会显示出来。比如,为了插入一个Application目录下用“app”为前缀的snippet,你能键入“app+?+Tab”或者“a+?+Tab”来触发完整的快捷键列表。 快捷键列表呈现,当前选中的snippet的title显示在tooltip的最上部。然后你就能双击或者按回车键来将snippet插入你的代码。
Snippet Shortcut List
大多数我们讨论的snippet插入方法都涉及到某种键入,但是仍然有一种特殊情况。那就是:拖放。其实code snippet是以.snippet为扩展名的xml文件。一旦你安装了Visual Basic,这种文件就会被自动拷贝到你的电脑。Code Snippets Manager显示了那些snippet文件的路径,这样你就能在磁盘上找到它们。一旦你确定了路径比并且用windows Explorer将你的snippet文件放置于磁盘上,你就能简单地将Snippet拖拽到你的代码中。
[原文作者]:Milind Lele
08年11月19日12:39PM 上传
VS的2010版本(点击此处下载)比以前的一个改进的地方是WPF的拖拽式数据绑定,WinForms的拖拽式数据绑定在Visual Studio 2005就已经存在了。我们在VS2010把拖拽式数据绑定应用到了WPF上。如果你熟悉WinForms的数据绑定,你会发现WPF的拖拽式数据绑定和WinForms的很相似。
VS2008 SP1让我们可以在工程中添加一个EDM(实体数据模型),在VS2010中,添加了在数据源窗口中支持EDM这一功能,所以如果添加了一个EDM到工程中,这个EDM可以显示在数据源窗口中,你知道拖动这个EDM到WPF窗体中就可以完成绑定了。
你可以下载CTP体验一下,然后告诉我你的想法。
[原文作者]:Mary Lee
假定你已经使用免费的Visual Studio Express Edition开发了一个程序,现在你想对公众部署这个程序。
我们的话题就此开始!
创建一个新的部署工程:
1. 在“File”菜单里,点击“Add”,并单击“New Project”;
2. 在弹出“Add New Project”对话框的“Project Type”面板中,展开“Other Project Types”结点,然后选择“Setup and Deployment Projects”;
3. 在“Templates”面板中,选择你想要创建的部署工程类型;
在这个例子中,我使用的版本是Visual Basic 2008 Express Edition,所以当我打开“New Project”对话框的时候,没有“Other Project Types”这个结点。
只有在标准版(Standard Edition)或者更高版本(比如Professional Edition 或 Team System Edition)中才有“Other Projects Types”这个结点。同样地,你也不能下载Setup Project模板并添加到Visual Studio Express Edition版本中。
说到这里,你发现Visual Studio Express Edition虽然是免费,但功能受限了吧!下面列出的所有版本的功能都受到同样限制:Visual Basic 2005 Express Edition,Visual C# 2005 Express Edition, Visual C++ 2005 Express Edition, Visual Basic 2008 Express Edition,Visual C# 2008 Express Edition, and Visual C++ 2008 Express Edition。
希望总是有的!使用
ClickOnce就可以轻松地与大伙共享你的程序。它的发布向导能够轻松地创建一个安装程序来核查并安装所需的初始环境,比如.NET Framework 或 SQL Server Express Edition。发布的文件可以拷贝到CD光盘,本地Web服务器或者文件分享网络,以便用户们可以下载并安装你的程序。如果你总结客户的使用反馈后给程序增加了新的功能特性,ClickOnce还可以轻松地更新你的程序!
我制作了一个网络浏览器,现在我要教你使用Visual Basic 2008 Express Edition发布向导来部署这个程序到文件分享网络上去。
1. 展开Solution Explorer,右击解决方案名字并点击 “Publish”。
接着你就看到发布向导Publish Wizard 打开了。
2. 输入你想要Visual Studio拷贝文件的位置,并单击“next”。
3. 输入你的最终用户的起始安装位置,并单击“next”。
这个位置叫做安装位置。这两个位置是分开的,以防你可能没有对文件共享网络的写入权限。在那种情况下,你只好将文件拷贝到本地,管理员会拷贝这些文件到文件共享网络中去。
4. 选择程序开始运行的路径,并单击“Next”。
如果你选择的是“available online or offline”,该程序将要被安装到最终用户的计算机。如果你选择“only available online only”,该程序只能从安装位置运行。
5. 在发布向导的最后一页,单击“Finish”完成即可。
6. 打开网络文件夹,核查所有文件已经拷贝。最终用户可以打开Browser. application, publish.htm,或 setup.exe files 来开始安装。
好,我们现在对安装过程进行测试。
7. 打开程序安装文件所在的位置。
8. 在 publish.htm文件中,单击“ launch” 或“Install”。
稍后你就看到Launching Application对话框
9. 在安全警告窗口中, 单击 “Install”。
[原文作者]:Mary Lee
答案是:当你抛出它的时候
从CLR 2.0开始,触发StackOverflowException异常的条件就改变了,用户不能触发这个异常(备注1),如果发生异常的话取而代之的是CLR会直接结束掉发生异常的进程。
但是事实并不是100%如此,用户代码仍然能够人为抛出StackOverflowException异常,当然不是通过真正的溢出错误,而是用户手动调用。这和文档上说得不一样,这里我们用一个不太规范但是很简单的程序来说明这种情况。(见备注)
当然,这种用法没什么特别的,但是我还是觉得有必要提到这点,因为最近我看了一个新讨论组的谈话记录,有人发布了些简单的异常触发代码,碰巧里面提到StackOverflowException。他的代码里直接抛出了这个异常,而且他也有足够的理由相信这个异常在产品代码中也可以被抛出,对此我真的感到非常惊讶。
请不要认为这篇文章提倡用户来抛出StackOverflowException异常(你当然不应该这么做),所有介绍都只是我的一点兴趣。坦白说我希望任何情形这个异常都是不能被捕捉的。
public static void CatchStackOverflow1() {
try {
throw new StackOverflowException();
} catch (StackOverflowException ex) {
// Executes and handles the exception. User code continues
Console.WriteLine(ex.Message);
}
}
static int CreateRealOverflow(int p1) {
return 42 + CreateRealOverflow(p1 + 1);
}
public static void CatchStackOverflow2() {
try {
CreateRealOverflow(42);
} catch (StackOverflowException ex) {
// Will not execute
Console.WriteLine(ex.Message);
}
}
static void Main(string[] args) {
CatchStackOverflow1();
CatchStackOverflow2();
}
[1]除非你可以在CLR托管的情况下执行一些回收机制,这当然是一个例外而不是规则规定的
大多数XSLT程序员对于下面这段复制一个XML文件的XSLT转换都非常熟悉。
<?xmlversion="1.0"encoding="utf-8"?>
<xsl:stylesheetversion="1.0"xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:outputmethod="xml"indent="yes"/>
<xsl:templatematch="@* | node()">
<xsl:copy>
<xsl:apply-templatesselect="@* | node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
这种XSLT身份转换的应用十分普遍,因为它可以让你复制整个XML文档,并且会“访问”到每一个XML节点和属性。如果你添加一个模板,那么它就会对那些存在匹配的节点或属性进行转换,而那些不存在匹配的节点和属性只会被简单的拷贝。
我们也可以用Visual Basic 中的XML Literals(包括LINQ到XML和XML的轴属性)来实现XSLT的转换,VB代码可以通过基于以下伪码的XML文档以递归的方式“访问”到每个节点或元素:
Starting with the root element, perform the following whenever you encounter a node
If the node is an element
If the element has attributes, transform or copy each attribute
If the element has child nodes, transform or copy each node
If the node is text, transform or copy the text
If the node is CData, transform or copy the CData
If the node is a comment, transform or copy the comment
If the node is a processing instruction, transform or copy the processing instruction
对于手册中的这一方法,我们将创建一个抽象(MustInherit)基类来执行上面XML文档中的这段递归调用的伪代码。然后再创建一个继承于这个基类的子类来执行具体的转换。首先,我们将创建那个抽象基类和一个作为“起始点”的Transform函数,它以要被转换的XML文档(XDocument)作为输入,然后将转换后的文档返回。
Public MustInherit Class VBXmlTransform
Public Overridable Function Transform(ByVal xmlDoc As XDocument) As XDocument
Return <?xml version="1.0" encoding="utf-8"?>
<%= ProcessElement(xmlDoc.Root) %>
End Function
End Class
接着,我们来添加那段要调用到每个XML节点(XNode)的逻辑代码,它包含了elements, text, CData等等。在代码中我们需要判断XML节点的类型从而调用相应的函数来进行转换或者对节点类型进行拷贝,并且返回结果,这个结果可能是一个拷贝而来的节点,也可能是一个经过转换的节点。这个方法即ProcessNode,下面是它的代码:
Public Overridable Function ProcessNode(ByVal xmlNode As XNode) As XNode
' This method ignores DTD (XDocumentType) content.
Dim nodeType = xmlNode.GetType()
' Because XCData inherits from XText, check for the XCData type before checking
' for XText.
If nodeType Is GetType(XCData) Then Return ProcessCData(xmlNode)
If nodeType Is GetType(XText) Then Return ProcessText(xmlNode)
If nodeType Is GetType(XElement) Then Return ProcessElement(xmlNode)
If nodeType Is GetType(XComment) Then Return ProcessComment(xmlNode)
If nodeType Is GetType(XProcessingInstruction) Then Return _
ProcessProcessingInstruction(xmlNode)
Return xmlNode
End Function
接下来,我们添加一个强类型函数来处理每种节点类型以及属性。由于处理element的函数和其他的不一样,所以我们将稍后给出。处理其他的节点类型和属性的函数相当简单,因为基类的行为就只是拷贝文档,即每一个函数仅仅将其输入值返回。所以我们创建这段代码的目的也就是为了提供一个强类型函数,可以让我们在继承于它的子类的具体方法中来进行覆盖。下面是那些强类型函数(除ProcessElement函数):
Public Overridable Function ProcessAttribute(ByVal xmlAttribute As XAttribute) As XAttribute
Return xmlAttribute
End Function
Public Overridable Function ProcessCData(ByVal xmlCData As XCData) As XCData
Return xmlCData
End Function
Public Overridable Function ProcessText(ByVal xmlText As XText) As XText
Return xmlText
End Function
Protected Overridable Function ProcessComment(ByVal xmlComment As XComment) As XComment
Return xmlComment
End Function
Public Overridable Function ProcessProcessingInstruction( _
ByVal pi As XProcessingInstruction) As XProcessingInstruction
Return pi
End Function
现在让我们来看看那个ProcessElement函数,处理element的方法之所以独特是因为它不仅有属性还有child节点,这些属性和child节点都需要被转换或者拷贝,所以我们必须为每个属性提供ProcessAttribute函数,并且为每个child节点提供ProcessNode函数。我们将把这些代码放入一个名为CopyElement的方法中,这样ProcessElement函数看起来就和其他的强类型函数一样,但是它将返回一个对CopyElement函数的调用,而不仅仅返回其输入值。CopyElement函数使用了XML Literals,嵌入式表达以及LINQ到XML来实现对XML element的拷贝,代码如下:
Public Overridable Function ProcessElement(ByVal xmlElement As XElement) As XElement
Return CopyElement(xmlElement)
End Function
Public Overridable Function CopyElement(ByVal xmlElement As XElement) As XElement
Return <<%= xmlElement.Name %>
<%= From attribute In xmlElement.Attributes() _
Select ProcessAttribute(attribute) %>>
<%= From node In xmlElement.Nodes() _
Select ProcessNode(node) %>
</>
End Function
上面就是我们所说的抽象类了,现在我们可以利用它来进行一些简单或复杂的身份转换了。让我们来看个例子吧:
创建一个转换
我们的例子将使用和手册1中相同的XML文件,它混合了来自几个不同命名空间的内容,从而提供了一个非常好的例子。这些示例文件中的内容在
< AdditionalContactInfo > element中可以找到: http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ContactInfo schema。在contact info文件中有address信息,它包含了三种不同的元素:homePostalAddress ,physicalDeliveryOfficeName 和registeredAddress。addressType类型含有一个必需的元素PostalCode,下面我们可以创建一个简单的类来对<PostalCode>进行转换并将它重命名为<ZipCode>。
首先,我们需要导入那些源文档中不同的schemas,这些源文档中特定的schemas,对于抽象的VBXmlTransform类而言并不需要,但对我们继承而来的子类却是必需的。
Imports <xmlns="http://SampleSchema/AWContacts">
Imports <xmlns:aci="http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ContactInfo">
Imports <xmlns:act="http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ContactTypes">
Imports <xmlns:crm="http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ContactRecord">
接下来,我们创建一个继承于VBXmlTransform的类,这个子类叫做AWTransform,在这个子类中,我们覆盖了基类并且添加了新的代码,从而可以执行我们想要的任何转换。这样,我们就必须覆盖掉基类的ProcessElement方法,因为我们需要寻找所有名为PostalCode的元素,如果找到了就进行转换,如果没有找到,才会用基类的ProcessElement方法来处理。
Class AWTransform
Inherits VBXmlTransform
' Rename <act:PostalCode> to <ZipCode>.
' Create an XName object to use for comparisons. This will perform better than comparing
' xmlElement.Name.LocalName to a string.
Private postalCodeXName As XName = _
XName.Get("PostalCode", _
"http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ContactTypes")
Public Overrides Function ProcessElement(ByVal xmlElement As XElement) As XElement
Select Case xmlElement.Name
Case postalCodeXName
Return TransformPostalCode(xmlElement)
Case Else
Return MyBase.ProcessElement(xmlElement)
End Select
Return Nothing
End Function
Public Function TransformPostalCode(ByVal postalCodeElement As XElement) As XElement
Return <ZipCode><%= postalCodeElement.Value %></ZipCode>
End Function
End Class
转换文档
为了实现转换文档,我们需要新建一个子类的实例AWTransform,然后将那个XML源文档传给Transform方法,代码如下:
Dim xmlPath = My.Application.Info.DirectoryPath & "\..\..\AWContacts.xml"
Dim savePath = My.Application.Info.DirectoryPath & "\..\..\TransformSave.xml"
Dim xmlDoc = XDocument.Load(xmlPath)
Dim transform As New AWTransform()
Dim transformedDoc = transform.Transform(xmlDoc)
transformedDoc.Save(savePath)
其他示例
让我们来看看一些其他可以添加到AWTransform类的例子。
以下这段代码实现了我们怎样去转换一个现有属性的内容:如果transform找到名为date的属性,就将date值转换为一般的日期和时间格式。
Private dateXName As XName = XName.Get("date")
Public Overrides Function ProcessAttribute(ByVal xmlAttribute As XAttribute) As XAttribute
If xmlAttribute.Name.Equals(dateXName) Then Return TransformDateAttribute(xmlAttribute)
Return MyBase.ProcessAttribute(xmlAttribute)
End Function
Public Function TransformDateAttribute(ByVal dateAttribute As XAttribute) As XAttribute
Dim dateValue As New DateTime()
If DateTime.TryParse(dateAttribute.Value, dateValue) Then _
dateAttribute.Value = dateValue.ToString("G")
Return dateAttribute
End Function
接下来的这段代码实现了我们怎样从转换后的文档中去掉data:如果transform找到
CData section就返回Nothing,这样CData section就不会存在于结果文档中。
Public Overrides Function ProcessCData(ByVal xmlCData As XCData) As XCData
Return Nothing
End Function
一言以蔽之:不会,任何情况都不回被截短。
我和同事前几天聊到一个有趣的案例,它涉及到IntPtr, Pinvoke以及64bit整体概念的准确性。 最终这场讨论把我们引向对IntPtr处理long类型的构造函数。令我吃惊的是,这个构造函数的是这么写的:
问题在于long类型的值被肆意地截短成一个int值。这将直接导致丢失一切越过4G的内存地址 (换言之,没有64bit的寻址)。这个确确实实放在眼前的大漏洞,使我想到这是不是有可能是个反编译器(disassembler)的bug。于是我用了.NET Reflector的IL模式。
L_0000: ldarg.0
L_0001: ldarg.1
L_0002: conv.ovf.i4
L_0003: conv.i
L_0004: stfld void* System.IntPtr::m_value
L_0009: ret
这段IL证实了long值确确实实是被截短了(还在开始处做了一个溢出检测)。但是且慢,mscorlib.dll是个与处理器运行模式紧密相关的特殊的DLL,这一切有可能仅仅是32位操作系统惹得祸。于是我切换到一台64bit的机器,重新打开Reflector,令我气恼的是面对我的依旧是惊人相同的代码。
几分钟后我想到打开任务管理器(task manager)看看,竟然看到reflector是个运行在WoW64的进程。这意味着reflector还是调用了32位版本的mscorlib.dll。顺利成章地,我用ildasm打开了64位的mscorlib,同时看到在64位模式,内存地址将不再被截短。
IL_0000: ldarg.0
IL_0001: ldarg.1
IL_0002: conv.u
IL_0003: stfld void* System.IntPtr::m_value
IL_0008: ret
conv.u 是一个非托管的unsigned int的惯用方法,在64位平台上,conv.u将是一个unsigned 8 字节数值 (详见:OpCodes.Conv_U)
概括一下,对于开发人员来说,IntPtr(long)的在不同的平台上做着完全正确的事,实现因平台不同而异。在32位操作系统上,如果一个非4GB的内存地址被当做参数传入的话,实现代码将(正确地)抛出一个异常。在64位的世界,实现代码将按照程序员的意愿返回一个正确的地址,不去截短任何值。
[原文链接]:VB XML Cookbook, Recipe 4: Get Inner XML from the Nodes Property (Doug Rothaus)
[原文作者]:Doug Rothaus
第三章将电子邮件对象通过特性转换将AdventureWorks示例文档转换成以原版格式保存的HTML 文档并提供它的超链接。然而,为了让第三章所作的事情简单化,我忽略了这样一件事情,那就是<eMailAddress> 元素的schema(可以在ContactTypes.xsd schema 文件中找到)既允许<eMail> 元素包括邮件地址,也允许<SpecialInstructions> 元素作为一个辅助信息比如用电话号码将电子邮件地址替换掉,还有合适打电话等等。<SpecialInstructions> 元素可以包含任何ContactTypes.xsd 中其他元素的ID. 为了让<eMailAddress> 元素包含所有信息,我们必须还要包含一些特别的指导。(注:你可以从 手册3 的文章中下载XML文档以及相关的schemas 文件)。
从电子邮件地址转换成<eMail> 元素非常简单,因为<eMail> 元素只包含电子邮件地址的值。然而要转换<SpecialInstructions> 元素却并非那么简单因为元素本身既包含子元素也包含值。如果你想得到<SpecialInstructions> 元素的“内嵌值”, 你就会得到元素里面的文本内容, 任何XML内容都不会被查找到.这也就是被称作是元素的内部内容。
如果你熟悉System.XML这个命名空间下的类的话,你将会立即想到这个方法。这个解决方案是针对元素中嵌入的XML内容而不是针对文本内容(值属性)。然而System.XML类既公开InnerText属性也公开InnerXml属性,但是却不支持LINQ到XML.嵌入的文本内容将由Value 这个属性来返回,嵌入的XML内容却由Node 这个属性。它们有何不同?InnerXml 将返回一个XML格式的字符串,它必须被重新转换成XML对象。Node 属性返回的是一个XML对象的LINQ集合,对于它你可以熟练操作或者逐个字符地整个地嵌入到XML中,这样就让Node 属性成为XML转换的有效工具。
所以,结合我们已经知道的,让我们看一看一个例子中的Value属性(嵌入文本)和Node(嵌入XML)属性。我们这里有一个Function(模板),TransformEmail, 它将把XML数据源转换为HTML.在这里我们用Value 属性得到了电子邮件地址,并且我们调用了GetSpecialInstructions 方法将需要包含的特有信息也嵌入到了HTML之中。GetSpecialInstructions 方法返回HTML 标记和<SpecialInstructions> 元素的内部XML,因为这些内部XML还包含其它混合标记,包括文本以及其他XML内容。
Private Sub TransformEmail(ByVal email As XElement)
Dim emailHtml = <div class="Email">
<a href=<%= "mailto:" & email.<act:eMailAddress>.Value %>>
<%= email.<act:eMailAddress>.Value %>
</a> 
<%= GetSpecialInstructions(email.<act:SpecialInstructions>) %>
</div>
email.ReplaceWith(emailHtml)
End Sub
Private Function GetSpecialInstructions( _
ByVal instructions As IEnumerable(Of XElement)) As IEnumerable(Of XElement)
If instructions IsNot Nothing Then _
Return From instruction In instructions _
Select <span class="SpecialInstructions">
<%= instruction.Nodes %>
</span>
Return Nothing
End Function
[原文作者]:Doug Rothaus
[原文链接]:VB XML Cookbook, Recipe 5: The “Halloween” Problem (Doug Rothaus)
在前两次的手册中,我们讲了关于用ReplaceWith这个方法来进行身份转换.如果这个方法满足了你的需要,它会在你的代码中引发另外一个问题--"Halloween".让我们看看这个问题到底是怎样的,怎么样去解决.(详细的"Halloween"问题和解决方案可以参考这个文档).
"Halloween"描述的场景是,你有一个以某种方式更新的数据集同时这个数据集又在被遍历的话,你会得到一个空引用的异常,更坏的是,你的代码可能会因为修改错误的数据而崩溃.例如前面两个手册用到的代码段:
Private Sub Recipe5(ByVal xmlPath As String)
Dim xmlDoc = XDocument.Load(xmlPath)
Dim info = xmlDoc.<Contacts>.<Contact>.<aci:AdditionalContactInfo>
' Replace e-mail address tags with mailto links.
For Each email In info...<act:eMail>
TransformEmail(email)
Next
End Sub
Private Sub TransformEmail(ByVal email As XElement)
Dim emailHtml = <div class="Email">
<a href=<%= "mailto:" & email.<act:eMailAddress>.Value %>>
<%= email.<act:eMailAddress>.Value %>
</a> 
</div>
email.ReplaceWith(emailHtml)
End Sub
如果你运行这段代码,就会得到下面的异常:

这个异常的出现是因为For... Each循环正在循环用<eMail>元素查询出来的结果。在第一次调用TransformEmail这个方法的时候,<eMail>这个元素已经被HTML替换掉了,结果,这个查询引用的就是已经不存在的XML元素了。
要怎么解决这个问题呢?有几个解决方案,第一,你可能你已经注意到了在前面的手册中,把这个查询的结果用ToList方法返回一个List。像下面的代码
For Each email In info...<act:eMail>
改为:
For Each email In info...<act:eMail>.ToList()
返回一个List,表明要处理的就不再是一个查询结果,而更新就会作用到查询上面,例如用HTML替换XML,这样就不会影响到List。
其他比较普遍的解决方案就是你的代码不要修改原本的文件,把结果放到新的XML文件中。下一个手册我会讲怎么做。
我正在做一个关于用Visual Basic XML Literals取代XSLT转换的博客,我发现这个博客将会很长,所以,Avner Aharoni 和我谈了之后决定像几个月前我们做的LINQ操作指南一样把它分为多个条目并成一个系列。
先介绍一下VB XML的操作指南。这个操作指南介绍了如何在Visual Basic中使用XML Literals的快速而简单的方法。在很多情况下,我们将会参考有着直接或者是相近功能的XSLT和XPath。你将会发现用XML Literals来转换和翻译XML是一件快速而简单的事情,XML的轴属性,和LINQ到XML,这一切都是从Visual Basic 2008开始的。
首先介绍如何用XML Literals和LINQ执行简单的XML转换。每个例子都用了一个嵌入式表达,从LINQ队列或者其他的资源,比如属性或函数中返回一组XML Literals,只需几行Visual Basic代码就能完成整个XSLT的转换。
先看第一个例子,我们用了一个包含SQL Server的XML文件(AWContacts.xml)。每一个<Contact>标签都有一个<EmailAddress>的子标签,下面的代码创建了一个新的在代码中含有e-mail地址的XML文件。
Imports <xmlns="http://SampleSchema/AWContacts">
Public Class XMLCookbook
Private Sub Recipe1()
Dim xmlDoc = XDocument.Load("AWContacts.xml")
Dim emailDoc = <?xml version="1.0"?>
<EmailAddresses>
<%= xmlDoc.<Contacts>.<Contact>.<EmailAddress> %>
</EmailAddresses>
emailDoc.Save("EmailAddresses.xml")
End Sub
End Class
如果用XSLT进行转换,我们将要花费更多。在这里,你只是用了一个Visual Basic创建一个“template”函数,然后用XML Literals加载并保存XML,就像创建一个新的XML文件一样。XML Child Axisproperty引用了一组<EmailAddress>子标签,你也可以用embedded expression在XML文件中增加标签而不是<xsl:copy-of>,这样你就不用XSLT或Xpath转换这个文件。接下来我们看一个XSLT(Visual Basic代码)转换的例子。
Recipe1.xslt
<?xmlversion='1.0'?>
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:aw="http://SampleSchema/AWContacts"
xmlns:aci="http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ContactInfo"
xmlns:crm="http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ContactRecord"
xmlns:act="http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ContactTypes">
<xsl:outputmethod="xml"indent="yes"/>
<xsl:templatematch="aw:Contacts">
<EmailAddresses>
<xsl:copy-ofselect="aw:Contact/aw:EmailAddress"/>
</EmailAddresses>
</xsl:template>
</xsl:stylesheet>
Recipe1_XSLT Visual Basic Code
Public Class XMLCookbook
Sub Recipe1_XSLT()
Dim xslTransform As New System.Xml.Xsl.XslCompiledTransform
xslTransform.Load("recipe1.xslt")
Dim reader = Xml.XmlReader.Create("AWContacts.xml")
Dim sw As New System.IO.StreamWriter("Recipe1.xml")
Dim writer = Xml.XmlWriter.Create(sw)
xslTransform.Transform(reader, writer)
sw.Close()
End Sub
End Class
这里的XSLT转换出两端大量的代码,让我们花费了大量的工作创建VB版本。在Visual Basic中XML命名空间比XSLT(1.0)简单,我可以为XML元素定义一个缺省的XML命名空间而不需要特别的去定义一个XML命名空间。
还有重要的一点,在Visual Basic能为XML Literals提供只能的支持,通过增加在博客附件中的schema文件然后继承这些schemas,我得到一个完整的XML子元素的列表,欲了解更多的信息,请参考XML Intellisense in Visual Basic.
使用LINQ
然后,你可以用LINQ在转换中增加一些查询功能。在XSLT中,你可以很轻易的用 <xsl:for-each>循环和 <xsl:if>判断来完成这个功能
例如,在源文件中,每一个<Contact>标签都有一个<EmailPromotion>的子标签,这个标签确定了是否希望从该公司接收e-mail。下面的代码给出了一个例子,但是只包含了接受e-mail许可的e-mail地址。
Dim promoList = <?xml version="1.0"?>
<EmailPromotionList>
<%= From contact In xmlDoc.<Contacts>.<Contact> _
Where contact.<EmailPromotion>.Value > 0 _
Select contact.<EmailAddress> %>
</EmailPromotionList>
下面是相比较的XSLT代码:
<xsl:templatematch="aw:Contacts">
<EmailAddresses>
<xsl:for-eachselect="aw:Contact">
<xsl:iftest="aw:EmailPromotion > 0">
<xsl:copy-ofselect="aw:EmailAddress"/>
</xsl:if>
</xsl:for-each>
</EmailAddresses>
</xsl:template>
上一个例子中,我们进一步的把源XML文件转化成一种新的格式。也可以用一个LINQ查询返回一组XMLLiterals。用嵌入式表达复制源文件中的值到新的XML Literal的元素或属性中。例如,新的文件将把<EmailAddress>替换为<Email>,原本<EmailPromotion>包含的信息转换为<Email>中的属性。
Dim transformList = <?xml version="1.0"?>
<EmailPromotionList>
<%= From contact In xmlDoc.<Contacts>.<Contact> _
Where contact.<EmailPromotion>.Value > 0 _
Select <Email
promotion=<%= contact.<EmailPromotion>.Value %>>
<%= contact.<EmailAddress>.Value %>
</Email> %>
</EmailPromotionList>
下面是相比较的XSLT代码:
<xsl:templatematch="aw:Contacts">
<EmailPromotionList>
<xsl:for-eachselect="aw:Contact">
<xsl:iftest="aw:EmailPromotion > 0">
<Email>
<xsl:attributename="promotion">
<xsl:value-ofselect="aw:EmailPromotion"/>
</xsl:attribute>
<xsl:value-ofselect="aw:EmailAddress"/>
</Email>
</xsl:if>
</xsl:for-each>
</EmailPromotionList>
</xsl:template>
总之,我们看到如何使用Visual Basic, XML Literals和LINQ来代替<xsl:copy-of>, <xsl:for-each>, <xsl:template>, <xsl:if>, <xsl:value-of> 和 <xsl:attribute>标签,并用XML轴属性代替Xpath去创造一个强大又简单的XML转换工具。
请看更多。。。。
[原文作者]:Amanda Silver
[原文链接]:Hidden Gems in Visual Basic 2008
昨天,我答应写一篇关于 Visual Basic 和 Visual Studio 2008里隐藏彩蛋的文章,这些彩蛋是你们在博客帖子或会议上从未见过的。我曾提及到了一些我最喜爱的特性,在BataⅠ版本的阐述中我尽情炫耀了智能感知(intellisence)的改进,尤其是对关键字、本地变量和表达式,自动完成语句的改进。在那篇文章中,我也提到一种语法新特性-非严格委托(Relaxed Delegates),它允许你为事件(event)提供一个可替代的签名(alternate signatures)。这些都是很棒的特性--但我要告诉你一些“新”玩意!
经过队员们的民意调查,最终我们挑选出了10个最重要的特性,我以前的文章里没有提及过的。我这里只是简单介绍,有兴趣的朋友可以继续研究。首先 ,我必须从基调特性(keynote feature)开始讲……
0) 多个 target Framework 支持(Multi-targetting)
简单的说,Multi-targetting支持你在VS2008或VB9下用.NET 2.0 framework 进行编译。下面我将要讲到的所有特性都在.NET 2.0下有效,所以当你打开Visual Studio 创建一个project以后,把framework target设置为2.0(除了第5和第7项,因为它们需要LINQ到Objects和XML API而LINQ是在.NET 3.5才推出)。
如
1) 输入推断(Type Inference)
在Visual Basic 9里面,下面的这一小段代码以及Nothing都是延迟绑定(late-bound)——在编译时(compile tine)一切都是延迟绑定,这就意味着你可以得到智能感知和输入推断。
Dim dialog = New OpenFileDialog()
Dim result = dialog.ShowDialog()
Dim printStr = "C:\"
If result = Windows.Forms.DialogResult.OK Then
printStr = dialog.FileName
End If
MsgBox(printStr)
这使得在输入代码时更加快捷,简单和准确。 这个特性和控制它的机制你可以在我们QA team里的Bill Horst写的文章里得到详尽描述。
2) IF操作符
还记得吗,IIF函数能返回一个对象(Object),这意味着你不需要通过智能感知或类型推断就可以得到默认的返回值(Object类型)。如果你坚持要类型安全或代码前绑定,可以强制转换,代码如下:
Dim intC As Integer = CInt(IIf(intA = intB, intA, intB - 1))
现在用IF操作符,你可以这么写:
Dim intD As Integer = If(intA = intB, intA, intB)
如果加上类型推断,代码就更简单了:

我本人是能提高易读性的特性的坚定拥护者。
3) 对象初始化(Object Initializers)
总的来说,在.net framework里,对象初始化是一种类似于把Dim和statement combined整合在一起的表达式。这样使得参数构造器多少让人容易接受一些:
Dim strm As New StreamWriter( _
New FileStream("C:\out.txt",FileMode.OpenOrCreate) _
With {.Position = 10})
对象初始化使得创建一个数组对象更加容易:
Dim Capitals() As City = {New City With {.Name = "Antanarivo", .Country = "Madagascar"}, _
New City With {.Name = "Belmopan", .Country = "Belize"}, _
New City With {.Name = "Monaco", .Country = "Monaco"}, _New City With {.Country = "Palau", .Name = "Koror"}}
4) 允许空值(Nullable)
Nullable 是一种特性,可能你知道但没有关注过。它是一个基本的.NET 表达式,专门针对nullable 类型(整数,日期,等等)。在 LINQ 和 SQL 中使用 Visual Studio 2008中引入的对象相关映射层这个设计器,数据库中的可空行都被映射成为这种类型。其结果是你可以在VB中写出像下面这样的表达式,而得到正确的结果--其中的空属性以null值传播。例如下面这点代码,在Country type中有个independence property 是nullable date。
Dim virginIslands As New Country With {.Independence = Nothing}
Dim palau As New Country With {.Independence = #10/1/1994#}
Dim vILength = #8/24/2005# - virginIslands.Independence ' Nothing
Dim pLength = #8/24/2005# - palau.Independence ' 3980.00:00:00
5) LINQ to DataSet
我爱死这个特性了,因为它意味着你可以不用调用其他数据访问技术就能收获LINQ的好处。我先填充了一个 DataSet,然后就可以对这个DataSet进行查询。
Me.EmployeesTableAdapter.Fill(Me.NORTHWNDDataSet.Employees)
Dim query = From emp In Me.NORTHWNDDataSet.Employees _
Where emp.Country = "USA" _
Order By emp.HireDate _
Select emp
Me.EmployeesBindingSource.DataSource = query
6) 语法提示(Syntax Tooltips )
再看看这个

还有这个

7) XML的namespace支持智能感知
之前我们已经在博客上讲到过XML Intelligence,但是我们忽视了一点。
当namespace被用在XML 文档中时,智能感知会对namespace前缀和local name进行匹配,你只要为输入带来很大便捷,你只要输入开头几个字母然后回车,VS会帮你找到匹配的字段并加上相应的前后缀。下面是个小例子,以一个输入文件开始,然后使用智能感知。
此时我们只输入字母t,VS会自动选中tomato。
8) 转到类型定义(GoTo Type Definition)
通常,当你定义了一个变量,你想通过Object Browser浏览它在代码中的类型定义的时候,现在你多了一种选择,通过context menu可以让你直接找到它的定义。这点非常好,尤其是涉及到类型推断时能帮你确定该变量的类型是否和你想象的一致。
9) 循环变量的输入推断(Type inference for loop variables)
检查下面这段代码:
还有这段:
如果没有指定控制变量的类型,它会根据表达式或循环的信息从右往左推断。
10) 性能的提高以及非封闭性操作
后台编译器有一个非常强大的特性,它可以给你及时地反馈只要你写的代码正确。在这个版本的Visual Studio中我们对后台编译器做了很大的改进,我们相信后台编译器比以前快3倍但只用原先1/3的内存。任何使用过VS2008 的人都会意识到这一点。虽然我们在性能上做了很大的改进,但是在大项目里面某些操作符仍然是一个巨大的花费,例如改变一个base class的declaration 通常会被多次用到。如果在后台编译器工作之前,你试图调用一些依赖于编译信息的特性(例如Intellisence或Drop Downs),在以前版本的VS中会有一个长时间的停顿直到编译完成为止,但是现在这个问题解决了,当你想得到drop downs的时候就像这样:
配料:
· Visual Studio 2008 (Beta2或更高版本)
类别:LINQ-ToXML
步骤:
· 使用VB 创建一个新的Console Application。
· 创建RSS文档。添加以下代码到Sub Main中。
Dim rss = <?xml version="1.0"?>
<rss version="2.0">
<channel>
<title>LINQ to XML in Visual Basic</title>
<description>Sample RSS Feed</description>
<language>en-us</language>
<pubDate>Tue, 18 Sep 2007 00:56:12 GMT</pubDate>
<item>
<title>Basic Instincts: Lambda Expressions</title>
<description>A new feature added to Visual Basic 9 to
support Language Integrated Queries (LINQ),
which adds data programmability to
Visual Basic.</description>
<pubDate>Thu, 27 Sep 2007 23:54:55 GMT</pubDate>
</item>
<item>
<title>Visual Basic Pack for Visual Studio 2005 SDK</title>
<description>The Visual Basic Pack for the Visual Studio
2005 SDK includes SDK samples converted into
the Visual Basic language and a new wizard for
generating Visual Basic-based integration
packages for Visual Studio.</description>
<pubDate>Mon, 17 Sep 2007 23:58:49 GMT</pubDate>
</item>
<item>
<title>XML to Schema Tool</title>
<description>The XML to Schema tool is a free project item
template to automate creation of XML schema
sets from any number of XML documents. If you
are working with Language Integrated Queries
(LINQ) to XML in Visual Basic 9, this utility
can significantly improve your editing
experience by adding XML schemas (.xsd files)
to your project that add IntelliSense for XML
properties.</description>
<pubDate>Mon, 17 Sep 2007 23:59:43 GMT</pubDate>
</item>
<item>
<title>Line and Shape Controls in the Power Packs</title>
<description>Download the latest version of the Visual
Basic 2005 Power Packs which now includes Line
and Shape controls that enable you to draw
lines, ovals, and rectangles on forms and
containers at design time making it much easier
to enhance the look of your user
interface.</description>
<pubDate>Mon, 20 Aug 2007 20:24:25 GMT</pubDate>
</item>
</channel>
</rss>
在VB中你可以使用LINQ语法查询XML就像你在SQL Server或者其它数据库上查询数据和数据集合一样。不同之处在于你如何去查找XML 元素和属性。在VB中有些XML 的特性允许你使用类数据对象那样查找XML 的元素和属性。
举例来说,要查找之前创建的XDocument对象中的<channel>元素,你只要编写一行简单的代码:rss.<rss>.<channel>.尽管这看起来只是查找出一个<channel>元素,但事实上代码返回的是<rss>对象中所有<channel>子元素的集合。这样你就能通过Index来查找特定的<channedl>元素,或者就像我们在标题中看的那样通过执行LINQ查询所有的元素集合。为了能查找XML元素的任意子元素,可以使用descendant语法"...",示例rss.<rss>...<title>.为了查找属性,可以使用attribute语法"@",示例rss.<rss>.@version。这些为了在XDocument或XElement中查找元素和属性的语法被称之为XML Axis属性。
注意:在VB中XML特性包含了XML智能感知,它可以在我们编码过程中使用XDocument或XElement这些LINQ to XML对象时提供它们的XML属性以及子元素列表。要启用XML智能感知只需将XML结构定义(XSD)文件添加到你的项目中。VB中的XML工具会在你没有XSD文件的前提下,通过XML文件的内容创建一个XSD文件。更多信息,请看XML IntelliSense in Visual Basic。
通过在VB中使用XML Axis属性,我们可以使用LINQ来查询XML内容。举例来说,以下的查询在RSS feed中执行了一次搜索,搜索的内容是<item>的<title>和<description>元素。添加代码到Sub Main中,并将其置于XDocument对象之后。
Dim itemList1 = From item In rss.<rss>.<channel>.<item> _
Where item.<description>.Value.Contains("LINQ") Or _
item.<title>.Value.Contains("LINQ")
Console.WriteLine("Items containing 'LINQ'" & vbCrLf)
For Each item In itemList1
Console.WriteLine(vbTab & "Title: " & item.<title>.Value)
Console.WriteLine(vbTab & "Description: " & item.<description>.Value)
Console.WriteLine()
Next
注意示例代码的搜索是从XML元素的Value属性寻找匹配值。Value属性将从XML元素中返回的内容做为字符串。如果不这样处理的话,XML Axis属性将会对应元素名称的元素集合。但是当搜索XML属性时,你并不需要指定Value属性。这种情况下,XML Axis属性默认返回第一个匹配属性的Value.
由于Value属性总是返回字符串,你或许需要将其转化成其它指定的类型来执行你的查询。举例来说,以 下代码示例搜索的是过去20天发布的RSS。添加代码到Sub Main末尾。
Dim itemList2 = From item In rss.<rss>.<channel>.<item> _
Let pubDate = DateTime.Parse(item.<pubDate>.Value) _
Where pubDate >= _
DateTime.Now.Subtract(New TimeSpan(20, 0, 0, 0)) _
Select item
Console.WriteLine("Items published in the last 20 days" & vbCrLf)
For Each item In itemList2
Console.WriteLine(vbTab & "Title: " & item.<title>.Value)
Console.WriteLine(vbTab & "Publish Date: " & item.<pubDate>.Value)
Console.WriteLine()
Next
按下F5查看代码运行情况。