随笔 - 89, 评论 - 163, 引用 - 33

导航

关于

标签

每月存档

最新留言

广告

新消息:开发者开始趋向于在VB.Net中进行关于XML的工作了

 
 
       Visual Basic 9.0 提供了一个新的功能XML Literals,它将使得对XML的编程更加简单方便,并且在很大程度上的减少了我们的代码量。实际上,XML Literals使得我们用Visual Basic去做XML变得如此简单,以至于很多C#开发者也开始趋向于在VB.NET中来进行XML相关工作!如果想要了解更多信息,请参看下面来自TechEd US的内容:

posted on 2008-12-31 13:47:32 by VBCTI  评论(2) 阅读(3306)

在VB中LINQ To Dataset如何工作

[原文作者]Jonathan Aneja
 
      LINQ的核心是要求可以对任何数据源进行可查询,意味着它必需实现IEnumerable接口。(实际上实现起来远比这个复杂,需要详细了解的请参阅Visual Basic 9.0 Language Specification)。现在当我们使用LINQ to Dataset时遇到了一个问题:DataTable没有实现IEnumerable,我们怎样去建立对它的查询呢?
之前我们了解到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()来使用针对DatasetLINQ
 
                Dim customers = TestDS.Tables("Customers")
 
                Dim franceCustomers = From cust In customers.AsEnumerable() _
                          Where cust!Country = "France" _
                          Select cust
 
      现在有一个有趣的事是在VBLINQ的工作方法,不需要在代码中明确地调用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 SamplesLINQ 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中的一个新类型,在VS2005Dataset设计器将生成继承自DataTable的代码,并且显式地实现IEnumerable
 
 

posted on 2008-12-17 17:30:52 by VBCTI  评论(0) 阅读(2455)

COM对象上的反射

[原文作者]Lucian
[原文链接]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

 

 

 

posted on 2008-12-17 17:27:49 by VBCTI  评论(0) 阅读(4897)

LiveRun – 即时查看程序输出的VS插件

         
      [原文作者]Lucian
      [原文链接]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 修复,代码改进以及评论!

posted on 2008-12-17 17:21:59 by VBCTI  评论(0) 阅读(4203)

TableAdapter和多组数据结果

[原文作者]Young Joo                                          
       
     很多人问我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()方法。
     假设这里有一个包含CustomersOrdersNorthwindDataset.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 classInitAdapter()方法,你就会看到类似的代码:
    tableMapping.SourceTable = "Table"
    tableMapping.DataSetTable = "Customers"
    '' Colum mapping code skipped
     ...
     Me._adapter.TableMappings.Add(tableMapping)
 
      这段代码是为了保证DataAdapter.Fill方法返回的数据表与Dataset里的数据表相对应。在我们FillCustomersOrders示例里,第二组结果包含的是Orders信息,所以我们在Table1Orders之间创建了映射关系,确保数据FillOrders表中。
     把以上代码添加到partial class后,你就可以调用FillCustomersOrders方法来fill CustomersOrders
     CustomersTableAdapter.FillCustomersOrders(Me.NorthwindDataSet)
 
性能的考虑
 
      有些情况下,这种方法的确很有效。但是这也要看情况,也许你会想到这个方法可以避免多次访问数据库,从而提高性能,但如果仅仅只需要获取一小部分数据,却应用这种方法一次读取了大量的数据,这同样也会降低性能,倒不如一次读取小部分数据,需要其它数据时,与数据库建立另一个连接,再读取。ADO.NET在处理多个数据库连接方面性能优化得还是不错的,很多情况下,都不至于导致性能瓶颈。总之我们只需要遵循最基本的原则:只在需要的时候,才去读取数据。
     但有些情况下,读取多组数据结果还是很有帮助的,所以,应用我在这里所介绍的方法吧,但时刻也不要忘记性能的问题。
 
相关资源

posted on 2008-12-17 17:13:00 by VBCTI  评论(0) 阅读(4044)

Co/Contra-Variance:如何将一个Apple列表转化为Fruit列表

[原文作者]:Lucian
 
    这是我们如何在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.

posted on 2008-12-17 17:07:39 by VBCTI  评论(0) 阅读(3685)

往代码中插入Snippet的方法

[原文作者]: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拖拽到你的代码中。

posted on 2008-12-17 17:02:00 by VBCTI  评论(0) 阅读(3597)

Visual Studio 2010 中WPF拖拽式数据绑定

[原文作者]: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窗体中就可以完成绑定了。
具体信息我发布在Visual Studio Data blog上了。
你可以下载CTP体验一下,然后告诉我你的想法。

posted on 2008-12-17 16:48:30 by VBCTI  评论(0) 阅读(4983)

Powered by: Joycode.MVC引擎 0.5.2.0