posts - 280, comments - 147, trackbacks - 1, articles - 21
  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理

2011年3月17日

SELECT
Q1.ProductName
,(Q2.M1 & Power(2,OrderDate)) *  SaleVolume AS M1
,(Q2.M2 & Power(2,OrderDate)) *  SaleVolume AS M2
,(Q2.M3 & Power(2,OrderDate)) *  SaleVolume AS M3
,(Q2.M4 & Power(2,OrderDate)) *  SaleVolume AS M4
,(Q2.M5 & Power(2,OrderDate)) *  SaleVolume AS M5
,(Q2.M6 & Power(2,OrderDate)) *  SaleVolume AS M6
,(Q2.M7 & Power(2,OrderDate)) *  SaleVolume AS M7
,(Q2.M8 & Power(2,OrderDate)) *  SaleVolume AS M8
,(Q2.M9 & Power(2,OrderDate)) *  SaleVolume AS M9
,(Q2.M10 & Power(2,OrderDate)) *  SaleVolume AS M10
,(Q2.M11 & Power(2,OrderDate)) *  SaleVolume AS M11
,(Q2.M12 & Power(2,OrderDate)) *  SaleVolume AS M12
FROM
(
SELECT
T3.[name] AS ProductName
,datepart(MM, T2.orderdate) AS OrderDate
,SUM(ISNULL(T1.totalretailvalue,0.00)) AS SaleVolume
FROM [Order] T1
INNER JOIN
OrderPart T2
ON T1.id = T2.[orderno]
INNER JOIN
m_product T3
ON T2.collectionid = T3.id
WHERE T1.isdeleted = 0 AND T3.id = $ProductId$ AND datepart(YEAR, T2.orderdate) = $Year$
GROUP BY T3.[name],datepart(MM, T2.orderdate)
) Q1

CROSS JOIN
(
SELECT 1 AS M1,2 AS M2,4 AS M3,8 AS M4, 16 AS M5,32 AS M6,64 AS M7,128 AS M8, 256 AS M9, 512 AS M10, 1024 AS M11, 2048 AS M12
) Q2

posted @ 2011-03-17 20:11 懒人ABC 阅读(110) 评论(1) 编辑

2010年9月7日

F# 入门
使用 .NET Framework 中的函数式编程技术
Ted Neward

本文讨论:
  • 安装 F#
  • F# 语言基础
  • .NET 互操作性
  • 异步 F#
本文使用了以下技术:
.NET Framework, F#
作 为 Microsoft® .NET Framework 家族的新成员,F# 提供类型安全、性能以及类似脚本语言的工作能力,所有这些都是 .NET 环境的一部分。此函数式语言由 Microsoft 研究院的 Don Syme 发明,作为 CLR 的 OCaml 语法兼容变体,但 F# 已经迅速地从科研转为投入实际应用。
随着函数式编程的概念通过 .NET 泛型和 LINQ 等技术越来越多地渗入主流语言(例如 C# 和 Visual Basic®),F# 在 .NET 社区里的知名程度也不断提高——因此,2007 年 11 月 Microsoft 宣布将 F# 确定为受支持的 .NET 编程语言。
多年来,大家一直认为函数式语言领域(ML、Haskell 等)更适合用于学术研究,而不适用于专业开发。但这并不代表这些语言没有过人之处。事实上,.NET 的一些重要的功能增强(例如泛型、LINQ、PLINQ 和 Futures)都是将一些函数式编程概念全新应用到语言所致。以往对这些语言的关注程度不高主要是因为它们的目标平台与专为 Windows® 编写程序的开发人员关系不大、不能与底层平台很好集成,或者不支持关系数据库访问、XML 解析和进程外通信机制等主要功能。
但是,CLR 及其“多种语言,单一平台”的方法将使此类语言在 Windows 开发中的应用越来越广泛。并且顺理成章地引起在一线工作的程序员们的注意。F# 即是这样一门语言。在本文中,我将为您介绍一些 F# 的基本概念和优点。然后,为了帮助您初步了解 F#,我将详细介绍它的安装过程并编写几个简单的小程序。

为什么要使用 F#?
对于小部分 .NET 程序员来说,学习一门 .NET Framework 函数化语言无疑将使自己在编写功能强大软件方面前进一大步。而对其他程序员来说,学习 F# 的理由就因人而异了。F# 能为开发人员提供哪些益处?
随着多核 CPU 的普及,安全并发程序已成为过去三年来的关注焦点。函数式语言倡导一种固定不变的数据结构,可在线程和机器之间传递,而无需担心线程安全或原子访问,开发人员可以利用这一特点支持并发操作。函数式语言还可更轻松地编写更支持并发特性的库,如稍后将在本文中介绍的 F# 异步工作流。
尽管对于专攻面向对象开发的程序员而言,可能对这种语言感觉不是这么强烈,但在很多情况下,函数式程序确实可以简化某些应用程序的编写和维护。例如,编写一个将 XML 文档转换成其他格式数据的程序。虽然完全可以通过编写一个 C# 程序,让它解析整个 XML 文档并应用各种 if 语句确定在文档中的不同位置采取何种措施,但实际上更好的方法是编写可扩展样式表语言转换 (XSLT) 程序。当然,XSLT 肯定包含大量的内置函数机制,如同 SQL 一样。
F# 强烈建议不要使用空值 (null),而是提倡使用固定不变的数据结构。这些特性可以减少需要编写的特例代码量,从而有助于降低编程出错的频率。
使用 F# 编写的程序还更加简洁。您可以切实地从两方面减少键入的内容:击键次数更少并且必须要向编译器通告变量类型、参数或返回类型的位置点也更少。这意味着需要维护的代码将大大减少。
F# 具有与 C# 相似的性能特点。但是,与简洁程度相似的语言(特别是那些动态和脚本语言)相比,它的性能特点要好得多。并且,F# 也包含通过编写程序段并交互式执行查看数据的工具,这一点与许多动态语言类似。

安装 F#
F# 可从 research.microsoft.com/fsharp/fsharp.aspx 免费下载,它不仅会安装所有命令行工具,而且还会安装 Visual Studio® 扩展软件包,该软件包提供彩色语法突出显示、项目和文件模板(包括作为入门指南的详细 F# 示例代码)以及 IntelliSense® 支持。同时它还提供可在 Visual Studio 内部运行的 F# 交互式 shell,它使开发人员能够从源文件窗口中提取表达式、将表达式粘贴到交互式 shell 窗口,并立即得到代码段的结果——在类似增强的 Immediate 窗口中显示结果。
在我撰写本专栏时,F# 在 Visual Studio 内作为外部工具运行,所以它缺少某些开发人员能够从 C# 或 Visual Basic 中获得的无缝集成的能力。此外,F# 还缺少 ASP.NET 页面设计器支持。(这并不是说 F# 不能在 ASP.NET 中使用,完全不是。这仅表示 Visual Studio 并没有为 F# 提供类似 C# 和 Visual Basic 的那种现成拖放式开发体验。)
尽管如此,当前版本的 F# 还是可以在能够使用其他 .NET 兼容语言的任何位置使用。在接下来的几页中,您将看到一些示例。

您好,F#
介绍任何语言的特有方式就是通过那几乎成为标准的“Hello, World”程序。F# 也不例外:
printf "Hello, world!"
虽然不能引起您太大的兴趣,但这个很小的例子显示出 F# 属于不需要显式入口点(C#、Visual Basic 和 C++/CLI 都需要显式入口点)的语言;该语言假设程序的第一行即为入口点并将从此处开始执行。
要运行此程序,刚入门的 F# 开发人员有两个选择:编译或解释。在 F# 解释程序中运行此程序 (fsi.exe) 很简单。只需从命令行启动 fsi.exe 并在出现的提示中输入上面一行内容即可,如图 1 所示。
图 1 在 F# 解释程序中运行 'Hello, World' (单击该图像获得较大视图)
请注意,在 shell 中,每条语句必须以两个分号结尾。这是交互模式的特殊要求,编译过的 F# 程序并不需要使用这种方式。
要将此示例作为标准的 .NET 可执行程序运行,请正常启动 Visual Studio 并创建一个新的 F# 项目(可以在“其他项目类型”中找到这个项目)。最初,F# 项目包含一个 F# 源文件,称为 file1.fs。打开此文件将发现大量示例 F# 代码集合。浏览一下这些内容即可大概了解 F# 的语法结构。然后将整个文件替换成前面所显示的 "Hello, world!" 代码。运行此应用程序,毫无疑问,在控制台应用程序窗口中将出现“Hello, world!”。
如果更喜欢命令行方式,可以使用 F# 安装目录中 \bin 子目录里的 fsc.exe 工具编译这段代码。请注意:fsc.exe 与大多数命令行编译器的工作方式相似,它从命令行获取源代码并生成可执行程序作为结果。大多数命令行开关都有相关文档,如果曾经使用过 csc.exe 或 cl.exe 编译器,那您可能已经熟悉其中许多开关。不过请注意,F# 目前对 MSBuild 的支持尚不完美;在当前安装版本(撰写本文时为 1.9.3.7)中不能直接支持由 MSBuild 驱动的编译。
如果希望“Hello, world!”中更具图形化特点,F# 可以通过 CLR 平台(包括 Windows Forms 库)轻松地提供完整的逼真度和互操作性。尝试以下代码:
System.Windows.Forms.MessageBox.Show "Hello World"
利用 .NET Framework 类库和 F# 库的能力使得 F# 语言不仅对那些早已使用如 OCaml 或 Haskell 之类函数式语言进行数学和科学计算的社区极具吸引力,而且受到全世界现有 .NET 开发人员的青睐。

Let 表达式
让我们看看比传统“Hello, world!”更复杂一些的 F# 代码示例。请看以下代码:
let results = [ for i in 0 .. 100 -> (i, i*i) ]
printfn "results = %A" results
在该 F# 语法中,let 表达式是令人产生好奇的元素。它是整个语言中最重要的表达式。更正式地说,let 可以为标识符赋值。用 Visual Basic 和 C# 开发人员的行话来说,“它可以定义一个变量”。但这并不确切。在 F# 中,标识符包含两个要素。首先,标识符一旦定义就不能再更改。(这即是 F# 帮助程序员创建并发安全程序的方法,因为它不提倡可变的状态。)第二,标识符不仅可以是基元类型或对象类型(如 C# 和 Visual Basic 中所使用的类型),而且还可以是函数类型,这一点与 LINQ 相似。
同时还请注意,标识符从不显式定义为类型。例如,从不定义结果标识符;它将从后面表达式的右侧进行推断。这称为类型推断,并且它代表编译器分析代码、确定返回值和自动插入返回值的能力(这与新的 C# 推断类型表达式通过变量关键字进行推断的方法类似)。
Let 表达式不仅可以处理数据,还可以使用它来定义函数,F# 将函数看作第一级概念。下面的示例定义了一个加法函数,它使用两个参数 a 和 b:
let add a b =
    a + b
完全按照您预期的方式工作:将 a 与 b 相加,并将结果显式返回给调用者。这意味着从技术上讲,F# 中的每个函数都将返回一个值,即使返回的不一定是值,也会返回一个特殊的名称 unit。这将在 F# 代码中产生一些有趣的暗示,特别是与 .NET Framework 类库相交的部分。但目前,C# 和 Visual Basic 开发人员可以把 unit 大致看作是与 void 相同的类型。
有时函数应该忽略传递给它的参数。要在 F# 中达此目的,仅需使用下划线作为该参数的占位符即可:
let return10 _ =
    add 5 5

// 12 is effectively ignored, and ten is set to the resulting
// value of add 5 5
let ten = return10 12

printf "ten = %d\n" ten
与许多函数式语言类似,F# 允许根据其调用进行 currying(可以仅部分定义函数的应用),以便提供其余的参数:
let add5 a =
    add a 5
在某种程度上,这与创建一个接受不同的参数集并调用其他方法的重载方法相似:
public class Adders {
    public static int add(int a, int b) { return a + b; }
    public static int add5(int a) { return add(a, 5); }
}
但两者还是有一点细微的差别。请注意,在 F# 版本中,没有显式定义类型。这表示编译器将采用自己的类型推断方法确定 add5 的参数是否是与加上整数 5 兼容的类型,并确定是按照这种方式编译,还是将其标记为错误。事实上,F# 语言主要使用隐式类型参数化(即使用泛型)。
在 Visual Studio 中,将指针停放在前面所显示的 ten 的定义上时,将表明其类型声明为:
val ten : ('a -> int)
在 F# 中,这表示 ten 是一个值,一个获取任意类型的参数并产生整数结果的函数。这种记号语法大致等同于 C# 中的 <T> 语法,所以对 C# 函数最贴切的说法是:ten 是类型参数化方法的委托实例,您想要忽略其类型(但在 C# 规则下不可忽略):
delegate int Transformer<T>(T ignored);

public class App
{
  public static int return10(object ignored) { return 5 + 5; }

  static void Main()
  {
    Transformer<object> ten = return10;
    System.Console.WriteLine("ten = {0}", return10(0));
  }
}

关键字 For
现在让我们看一下第一个示例中的 for 关键字:
#light

let results = [ for i in 0 .. 100 -> (i, i*i) ]
printfn "results = %A" results
先看代码的顶部,注意 #light 语法。这是为非 OCaml 程序员开始使用 F# 而做的让步,放宽某些 OCaml 语言的语法要求,并使用大量空白定义代码块。虽然不一定必要,但对于那些原本使用 C# 或 Visual Basic 的普通开发人员来说,它确实使语法更易于解析,因此它常出现在 F# 示例和公开的代码段中,并已成为事实上的 F# 编程标准。(未来版本的 F# 可能会将 #light 确定为默认语法,代替其他类似方法。)
那个看似简单的 for 循环实际上并不简单。正式地说,这是生成列表,即将生成列表型结果的代码块的另一种说法。
列表是函数式语言中经常出现的一种原语结构,在这方面它与数组有许多相似之处。但是,列表不允许基于位置的访问(如 C# 中传统的 a[i] 语法)。列表可在函数式编程的不同位置出现,大多数情况中,可以将其看作是 F# 中与 .NET Framework 中的 List<T> 类似,但提供一些增强功能的同等项。
列表通常使用一些特殊类型,本例中标识符结果是聚合列表,特别地是 F# 将此聚合类型标识为类型 (int * int)。如果将此聚合列表看作是 SQL 中 SELECT 语句返回的一对列,它的含义就清楚多了。因此,示例本质上是创建一个包含 100 个项目的整数对列表。
通常,在函数式语言中,函数定义可以在出现代码的任何位置使用。因此,如果希望扩展前面的示例,可以编写以下代码:
let compute2 x = (x, x*x)
let compute3 x = (x, x*x, x*x*x)
let results2 = [ for i in 0 .. 100 -> compute2 i ]
let results3 = [ for i in 0 .. 100 -> compute3 i ]
遍历列表(或数组或其他一些重复结构)是函数式语言中很常见的任务,它已成为基本方法调用:List.iter。它仅简单地对列表中的每个元素调用一个函数。其他类似的库函数还可提供一些非常有用的功能。例如,List.map 将函数作为参数,并将该函数应用于列表中的每个元素,并返回该过程产生的新列表。

管道
让我们讨论 F# 中另一个结构——管道操作符,它通过类似命令 shell(如 Windows PowerShell®)管道的通道获取函数的结果,并将结果用作后一个函数的输入。我们来看图 2 中显示的 F# 代码段。该代码段使用 System.Net 命名空间连接 HTTP 服务器,获取相应的 HTML 并分析结果。
/// Get the contents of the URL via a web request 
let http(url: string) = 
  let req = System.Net.WebRequest.Create(url) 
  let resp = req.GetResponse() 
  let stream = resp.GetResponseStream() 
  let reader = new System.IO.StreamReader(stream) 
  let html = reader.ReadToEnd() 
  resp.Close() 
  html
let getWords s = String.split [ ' '; '\n'; '\t'; '<'; '>'; '=' ] s 
let getStats site = 
  let url = "http://" + site 
  let html = http url 
  let words = html |> getWords 
  let hrefs = html |> getWords |> List.filter (fun s -> s = "href") 
  (site,html.Length, words.Length, hrefs.Length)

请注意 getStats 定义中的 words 标识符。它获取从 URL 返回的 html 值,并对其应用 getWords 函数。我还可以编写定义读取:
let words = getWords html
两者等同。但是 hrefs 标识符显示了管道操作符的威力,通过管道操作符可以将任意多个应用程序连接起来。此处我获取 words 的结果列表,并将其通过管道传递给 List.filter 函数,该函数使用匿名函数查找单词 href,并在表达式为 true 时将其返回。并且,最重要的是,getStats 调用的结果将是另一个聚合 (string * int *int * int)。要使用 C# 编写,需要的远远不止 15 行代码。
图 2 中的示例还显示出更多 F# 与 .NET Framework 的兼容性,以下代码也表现出这一特性:
open System.Collections.Generic
let capitals = Dictionary<string, string>()
capitals.["Great Britain"] <- "London"
capitals.["France"] <- "Paris"
capitals.ContainsKey("France")
确实,这个示例除了练习 Dictionary<K,V> 类型外没有什么其他内容,但它显示出在 F# 中如何指定泛型(使用与 C# 相同的尖括号)、如何在 F# 中使用索引(同样与 C# 一样使用方括号),以及如何执行 .NET 方法(使用与 C# 中同样的点和圆括号)。事实上,这里仅有的新内容是使用左箭头操作符为可变值赋值。这一点是必需的,因为 F# 与大多数函数式语言一样,保留等号用于比较,以便保持数学符号含义:如果 x = y,则 x 与 y 的值相等,而不是将 y 的值赋给 x。(真正的数学家们早已对普遍存在或设想过的 x = x + 1 提出异议,甚至偷笑不已。)

F# 也能够处理对象
当然,并不是所有开始使用 .NET 的开发人员都愿意立即接受函数式的概念。事实上,大多数从 C# 或 Visual Basic 转向 F# 的开发人员都需要知道他们在使用这一新语言时可以保留原有的习惯。在某种程度上,这是完全可行的。
例如,请看图 3 顶部所示的二维向量的类定义。其中就有一些有趣的概念。首先,请注意其中没有显式构造函数体;第一行中的参数表明用于构造 Vector2D 实例的参数本质上就是构造函数。因此长度标识符,以及 dx 和 dy 标识符将成为 Vector2D 类型的私有元素,而 member 关键字则表明可以通过标准 .NET 属性访问获取的 Vector2D 外部可用成员。本质上,这段 F# 代码声明了您可在图 3 底部看到的内容(由 Reflector 报告)。
                                                            VECTOR2D IN F#
type Vector2D(dx:float,dy:float) = 
    let length = sqrt(dx*dx + dy*dy)
    member obj.Length = length
    member obj.DX = dx
    member obj.DY = dy
    member obj.Move(dx2,dy2) = Vector2D(dx+dx2,dy+dy2)

VECTOR2D IN C# (REFLECTOR>
[Serializable, CompilationMapping(SourceLevelConstruct.ObjectType)]
public class Vector2D
{
  // Fields
  internal double _dx@48;
  internal double _dy@48;
  internal double _length@49;

  // Methods
  public Vector2D(double dx, double dy)
  {
    Hello.Vector2D @this = this;
    @this._dx@48 = dx;
    @this._dy@48 = dy;
    double d = (@this._dx@48 * @this._dx@48) + 
               (@this._dy@48 * @this._dy@48);
    @this._length@49 = Math.Sqrt(d);
  }

  public Hello.Vector2D Move(double dx2, double dy2)
  {
    return new Hello.Vector2D(this._dx@48 + dx2, this._dy@48 + dy2);
  }

  // Properties
  public double DX
  {
    get
    {
      return this._dx@48;
    }
  }

  public double DY
  {
    get
    {
      return this._dy@48;
    }
  }

  public double Length
  {
    get
    {
      return this._length@49;
    }
  }
}
请记住,F# 与大多数函数式语言相似,提倡使用不变的值和状态。当查看图 3 中的代码时,这一点尤为明显,因为所有属性都为只读属性,并且 Move 成员不会修改现有的 Vector2D,而是从当前 Vector2D 创建新的副本,并在返回副本之前对其应用修改的值。
还请注意,F# 版本不仅具备整体线程安全性,而且完全可以通过传统的 C# 或 Visual Basic 代码进行访问。这为 F# 入门提供了一种简便方法:使用它定义想要或者需要线程安全和固定不变的业务对象或其他类型。虽然完全可以在 F# 中创建提供常用可变操作组(设置属性及类似操作)的类型,但需要更多的工作,而且需要使用 mutable 关键字才可完成。在当今并发问题成为日常工作主旋律的世界中,这正如许多人所要求的一样——默认固定不变,必需或想要时可变。
在 F# 中创建类型很有趣,但还是可以用 F# 去做那些传统 C# 或 Visual Basic 代码可以做到的工作,如创建简单的 Windows 窗体应用程序并从用户处收集输入,如图 4 所示。
#light

open System
open System.IO
open System.Windows.Forms
open Printf

let form = new Form(Text="My First F# Form", Visible=true)

let menu = form.Menu <- new MainMenu()
let mnuFile = form.Menu.MenuItems.Add("&File")
let filter = "txt files (*.txt)|*.txt|All files (*.*)|*.*"

let mnuiOpen =
  new MenuItem("&Open...",
    new EventHandler(fun _ _ ->
      let dialog = 
        new OpenFileDialog(InitialDirectory="c:\\",
          Filter=filter;
          FilterIndex=2, 
          RestoreDirectory=true) 
        if dialog.ShowDialog() = DialogResult.OK then
          match dialog.OpenFile() with
          | null -> printf "Could not read the file...\n"
          | s ->
            let r = new StreamReader(s) 
            printf "First line is: %s!\n" (r.ReadLine());
            s.Close();
    ),
    Shortcut.CtrlO)

mnuFile.MenuItems.Add(mnuiOpen)

[<STAThread>]
do Application.Run(form)

任何熟悉 Windows 窗体的开发人员都能够立即明白这些代码的含义:创建一个简单的窗体、填充一些属性、填入一个事件处理程序,并告诉应用程序开始运行,直到用户单击右上角的“关闭”按钮。由于标准元素与 .NET 应用程序相同,所以只需重点关注 F# 语法即可。
Open 语句的操作与 C# 中 using 语句的作用大致相同,本质上都是打开 .NET 命名空间以便在没有正式限制符的情况下使用。Printf 命名空间是 F# 原有的、技术上与 OCaml 模块具有相同名称的端口。F# 不仅具备完整的 .NET Framework 类库,而且还有最简洁的 OCaml 库端口,这使得熟悉该语言的程序员能够象使用 .NET Framework 一样对其运用自如。(致好奇心强的程序员:Printf 位于 FSharp.Core.dll 程序集中。)您完全可以根据个人偏好随时使用 System.Console.WriteLine。
窗体标识符的创建利用了 F# 命名参数,它等同于实例化对象,然后调用一系列属性集来为这些属性填充值。我在下面的几行中使用相同的方法创建对话框标识符。
mnuiOpen 标识符的定义包含令人感兴趣的结构,该结构对于熟悉 .NET Framework 2.0 匿名委托或 .NET Framework 3.5 中 lambda 表达式的开发人员来说并不陌生。构造与 Open MenuItem 关联的 EventHandler 时,您可以看到使用以下语法定义的匿名函数:
fun _ _ -> ...
类似于匿名委托,这段代码创建了一个将会在选中菜单项时调用的函数,但语法略有技巧性。
MenuItem 定义中对 EventHandler 的定义是忽略传递给它的两个参数的匿名函数,这两个参数巧妙地对应标准 EventHandler 委托类型中的发送方和事件参数。该函数规定显示新的 OpenFileDialog 并在单击“确定”时检查结果...如下所示:
if dialog.ShowDialog() = DialogResult.OK then
    match dialog.OpenFile() with
    | null -> printf "Could not read the file...\n"
    | s ->
        let r = new StreamReader(s) in
        printf "First line is: %s!\n" (r.ReadLine());
        s.Close();
将使用模式匹配检查结果,该方法是函数化语言世界中一项强大的功能。模式匹配表面上与 C# 中的 switch/case 在某些地方存在相似之处,但实际上它名副其实地完成模式匹配工作:它将值与各种不同的模式进行比较(这些模式不需要都是常量值),并执行匹配的代码块。因此,以此处所示的匹配块为例,OpenFile 的结果可以匹配两种可能的值:null 表示无法打开任何文件,或者分配任何非 null 值的 s,该值将随后用作 StreamReader 的构造函数来打开并读取给定文本文件的第一行。
模式匹配是大多数函数式语言的重要部分,对它做些研究是完全值得的。它的一个最常见的用途是与可辨识联合 (discriminated union) 类型(C# 或 Visual Basic 中枚举类型的不确切说法)配合使用:
// Declaration of the 'Expr' type
type Expr = 
  | Binary   of string * Expr * Expr
  | Variable of string 
  | Constant of int

// Create a value 'v' representing 'x + 10'
let v = Binary("+", Variable "x", Constant 10)
函数式语言中常用它来创建域特定语言的核心表示,开发人员可以使用它来编写更为复杂和强大的结构。例如,不难想象扩展此语法以创建完全计算式语言,并可简单地通过为 Expr 类型添加新元素而进一步扩展该语言。这里需要注意的是:使用 * 字符的语法并不表示使用乘法,它是函数式语言中用于指示某类型中包含多个部分的标准方式。
事实上,函数式语言已经非常普遍地应用于编写面向语言的编程工具(如解释器和编译器),并且 Expr 类型最终将成为语言表达式类型的完整集合。F# 通过其内置的两个工具:fslex 和 fsyacc(专为获得传统语言输入—lex 和 yacc 文件—并将其编译成 F# 代码以便简化操作而设计)使这一切变得更为简单。如果对此感兴趣,可以下载 F# 安装程序深入研究;特别是标准 F# 发行包中的 Parsing 示例将提供非常好的入门基础结构。
可辨识联合只是模式匹配的优势之一,另一项优势是表达式的执行,如图 5 所示。位于 eval 定义中的 rec 是必需的,它告诉 F# 编译器在定义主体内迭代调用 eval。如果没有它,F# 将期望出现一个名为 eval 的本地嵌套函数。实际使用时,我使用函数 getVarValue 为变量返回一些预定义的值,getVarValue 将检查 Dictionary,查找变量创建时确定的返回值。
let getVarValue v =
    match v with
    | "x" -> 25
    | "y" -> 12
    | _ -> 0

let rec eval x =
    match x with
    | Binary(op, l, r) ->
        let (lv, rv) = (eval l, eval r) in
        if   (op = "+") then lv + rv 
        elif (op = "-") then lv - rv 
        else failwith "E_UNSUPPORTED"
    | Variable(var) -> 
        getVarValue var
    | Constant(n) ->
        n

do printf "Results = %d\n" (eval v)

当调用 eval 时,它将得到值 v 并发现该值是一个 Binary 值。这与第一个子表达式匹配,该表达式随后把值 (lv, rv) 绑定到刚检查的 Binary 值左右两侧的计算结果。未命名的值 (lv, rv) 是一个聚合(本质上是代表多个部分的单个值),这一点与关系集或 C 结构相似。
当首次调用 eval l 时,来自 Binary 实例的 l 恰好是 Variable 类型,因此对 eval 的递归调用匹配该模式匹配块的分支。随后将调用 getVarValue,它会返回硬编码 25,该值最终将绑定到值 lv。对于包含值 10 的常量 r 来说顺序相同,因此它将绑定到 rv。然后执行代码块的剩余部分(if/else-if/else 块),熟悉 C#、Visual Basic 或 C++ 的开发人员可以很容易地读懂该代码块的含义。
这里需要再次强调的是:每个表达式都将返回一个值,甚至在模式匹配块内部也一样。在本例中,返回值是一个整型值,该值可能是运算得到的值、从变量中检索到的值或者是常量本身。这一点似乎更容易让习惯于面向对象或过程化编程的开发人员产生微词,因为在 C#、Visual Basic 或 C++ 中,返回值是可选的,并且甚至在指定返回值的情况下仍可以忽略返回值。在类似 F# 的函数式语言中,要忽略返回值需要显式编码表达方式。如果出现这种情况,程序员可以将结果传给名为 ignore 的函数,由它完成适当的操作。

异步 F#
目前为止,我对 F# 语法的介绍采用以下两种方式中的一种:或者使用相对简单的函数式结构,或者使其看起来比较初级且简洁,象是传统面向对象、.NET 兼容语言(C#、Visual Basic 或 C++/CLI)的变体。这种介绍很难推动在企业内采用 F#。
但是请看一下图 6。它可与前面两种形式截然不同。除了多处出现 ! 字符并使用 async 修饰符外,这是一段看起来相对比较直观的代码:加载源图像映像、提取其数据、将数据传递到独立的函数进行加工(旋转、拉伸或其他操作),并将数据写回输出文件。
let TransformImage pixels i =
  // Some kind of graphic manipulation of images

let ProcessImage(i) =
  async { use  inStream  = File.OpenRead(sprintf "source%d.jpg" i)
          let! pixels    = inStream.ReadAsync(1024*1024)
          let  pixels'   = TransformImage(pixels,i)
          use  outStream = File.OpenWrite(sprintf "result%d.jpg" i)
          do!  outStream.WriteAsync(pixels')
          do   Console.WriteLine "done!"  }

let ProcessImages() =
  Async.Run (Async.Parallel 
               [ for i in 1 .. numImages -> ProcessImage(i) ])

较不明显的是使用 async 修饰符使这段代码进入 F# 所称的异步工作流(与 Windows Workflow Foundation 无关)中,这意味着这些加载/处理/保存步骤的每一步都在 .NET 线程池的平行线程中执行。
为了使其更简单,看一下图 7 中的代码。这种特殊的顺序以相对简单且易于理解的方式显示出异步工作流。不用深究细节,我们就可以看出 evals 是一组待执行的函数,通过 Async.Parallel 调用使其中每个函数都在线程池中排队等待执行。当执行时,可以看出实际上 evals 中的函数与 awr 中的函数在不同的线程中(尽管由于 .NET 系统线程池的特性,部分或全部 evals 函数有可能在相同的线程中执行)。
#light
open System.Threading

let printWithThread str =
    printfn "[ThreadId = %d] %s" Thread.CurrentThread.ManagedThreadId str
    
let evals =
    let z = 4.0
    [ async { do printWithThread "Computing z*z\n"
              return z * z };
      async { do printWithThread "Computing sin(z)\n"
              return (sin z) };
      async { do printWithThread "Computing log(z)\n"
              return (log z) } ]

let awr =
    async { let! vs = Async.Parallel evals
            do printWithThread "Computing v1+v2+v3\n"
            return (Array.fold_left (fun a b -> a + b) 0.0 vs) }

let R = Async.Run awr
printf "Result = %f\n" R

这些函数在 .NET 线程池以外执行再次表明 F# 语言对运行时互操作性的支持非常出色。甚至函数式语言中以前专门为特殊实现(如线程)保留的领域也要依赖 .NET Framework 类库,这表明 C# 程序员能够使用 F# 库或模块,就像 F# 开发人员能够使用 C# 库一样。事实上,将来 F# 的功能(如异步任务)能够充分利用新的 .NET Framework 库(例如并行扩展库中的任务处理库)。

与 F# 合作
如果对 F# 还不是十分清楚,那还有许多有关 F# 语言的介绍,这些内容远远超出一篇文章所能容纳的范围;事实上,在新语法和全新的思考方式(函数式与过程式)之间,坦白说对于已经习惯 C# 或 Visual Basic 等面向对象的普通开发人员来说,要掌握 F# 还需要一些时间。幸运的是,F# 与 .NET 系统中的其他语言可完全互操作,这表示您可以利用许多现有的知识和工具帮助您将 F# 变为自己的编程工具。
F# 开发人员可以访问所有的基类库,并且由于 F# 支持一些过程式和面向对象式开发,完全可以考虑不经过编译,使用 F# 交互模型学习 F# 语法和 Windows Presentation Foundation、Windows Communication Foundation 或 Windows Workflow Foundation 的细节。
如前所述,开发人员可以在 F# 中编写供应用程序代码其他部分使用的业务对象。因为 F# 类型构造生成的类大部分与使用 C# 或 Visual Basic 构造的类相似,所以 NHibernate 这类的持久型库仍可顺利保留 F# 类型,从而使 F# 能够无缝注入其他正在使用的业务应用程序中。
只需学习一些 F# 的知识就可帮助您更好地理解 C# 和 Visual Basic 未来版本中的许多新特性,因为其中许多想法和概念——包括泛型、迭代器(C# 中的 yield 关键字)和 LINQ——都来源于函数式和 F# 团队所做的研究。无论您怎么看待函数式编程,它都已是既成的事实,并且还将继续发挥功能。

Ted Neward 是高级企业系统领域的专家级独立顾问。他编写(或与人合著)了多本著作,还是 Microsoft MVP 架构师、BEA 技术指导、INETA 讲师和 PluralSight 培训师。可通过 ted@tedneward.com 或访问 Ted 的博客 blogs.tedneward.com 与他联系。

posted @ 2010-09-07 00:20 懒人ABC 阅读(28) 评论(2) 编辑

2010年8月9日

最近通过工具发现序列化一个对象时总会报一个FileNotFoundException错误,但是不影响代码的功能。

然而为了完善它,找到微软自己的一片关于这方面的文章。在这里照抄给大家。

 

XmlSerializer 常见问题疑难解答

 

发布日期 : 6/30/2004 | 更新日期 : 6/30/2004

Christoph Schittko

适用于:
Microsoft ®Visual Studio ®.NET

摘要:Christoph Schittko 讨论了各种相关技巧,以便诊断在使用 .NET 框架中的 XML 序列化技术将 XML 转换为对象(以及反向转换)时发生的常见问题。

本页内容

简介简介
XmlSerializer 的内部工作方式XmlSerializer 的内部工作方式
序列化错误序列化错误
声明序列化类型声明序列化类型
反序列化 XML 时发生的问题反序列化 XML 时发生的问题
来自构造函数的异常来自构造函数的异常
小结小结
致谢致谢

简介

.NET 框架中的 XmlSerializer 是一种很棒的工具,它将高度结构化的 XML 数据映射到 .NET 对象。XmlSerializer 在程序中通过单个 API 调用来执行 XML 文档和对象之间的转换。转换的映射规则在 .NET 类中通过元数据属性来表示。这一编程模型带有自己的错误类别,开发人员需要了解如何诊断这些错误。例如,元数据属性必须描述序列化程序可以处理的 XML 格式的所有变体。本文研究了在使用 XmlSerializer 构建基于 XML 的解决方案时可能发生的各种错误,并且讨论了用来诊断这些错误的技巧和工具。

XmlSerializer 的内部工作方式

为了有效地解决在 XML 序列化过程中出现的问题,需要了解一下在非常简单的 XmlSerializer 接口的内部发生了什么事情。与传统的分析范型相反,.NET 框架中 System.Xml.Serialization 命名空间的 XmlSerializer 将 XML 文档绑定到 .NET 类的实例。程序员不再需要编写 DOM 或 SAX 分析代码,而是通过直接在这些类中附加 .NET 元数据属性来声明性地设置绑定规则。因为所有分析规则都是通过属性表示的,所以 XmlSerializer 的接口非常简单。它主要由两个方法组成:Serialize() 用于从对象实例生成 XML;Deserialize() 用于将 XML 文档分析成对象图。

在使用强类型的、能够完美地映射到编程对象的结构严谨的 XML 格式时,这种方法非常有效。如果格式由 W3C架构定义,并且该架构由不带混合型内容或且不过度使用通配符(xs:any 和 xs;anyAttribute)的 complexType 组成,则 XML 序列化是处理该数据的好方法。

面向消息的应用程序就是一个很好的例子,这些应用程序之间的交换格式已预先定义。因为许多消息驱动的企业应用程序都具有非常高的吞吐量要求,所以 Serialize() 和 Deserialize() 方法被设计为具有很高的执行速度。实际上,正是 XmlSerializer 为 System.Messaging 命名空间中的具有高度可伸缩性的库、ASP.NET Web 服务 BizTalk Server 2004 提供了动力。

为获得 XmlSerializer 的高性能,需要付出双重代价。首先是与给定 XmlSerializer 可以处理的 XML 格式有关的灵活性,其次是实例的构造需要进行大量的处理。

当您实例化 XmlSerializer 时,必须传递您试图通过该序列化程序实例进行序列化和反序列化的对象的类型。序列化程序将检查该类型的所有公共字段和属性,以了解一个实例在运行时引用哪些类型。接下来,它将为一组类创建 C# 代码,以便使用 System.CodeDOM 命名空间中的类处理序列化和反序列化。在此过程中,XmlSerializer 将检查 XML 序列化属性的反射类型,以便根据 XML 格式定义来自定义所创建的类。这些类随后被编译为临时程序集,并由 Serialize() 和 Deserialize() 方法调用以执行 XML 到对象的转换。

这个设置 XmlSerializer 的精巧过程和声明性编程模型导致了三类错误,其中一些错误可能很难解决:

  • 所生成的序列化类期望被序列化的对象完全符合元数据属性所定义的类型结构。如果 XmlSerializer 遇到未声明(显式声明或者是通过 XML 序列化属性声明)的类型,则对象将无法序列化。

  • XML 文档在以下情况下无法反序列化:该文档的根元素不能映射对象类型;该文档的格式不正确,例如包含 XML 规范中定义为非法的字符;该文档违反基础架构的限制(在某些情形下)。

  • 最后,序列化类的创建及其随后的编译可能由于多种不同的原因而失败。当传递给构造函数的类型或者由该类型引用的类型实现了不受支持的接口或者不能满足 XmlSerializer 施加的限制时,类的创建可能会失败。

    当附加的属性生成无法编译的 C# 代码时,编译步骤可能会失败。编译步骤也可能由于与安全有关的原因而失败。

下面各个部分将更深入地研究这些情况,并提供有关如何解决这些问题的指导和建议。

序列化错误

我们要研究的第一类错误发生在 Serialize() 方法中。当在运行时传递给该方法的对象图中的类型与在设计时在类中声明的类型不匹配时,将发生此类错误。您可以通过字段或属性的类型定义来隐式声明类型,也可以通过附加序列化属性来显式声明类型。

typedecl

1. 对象图中的类型声明

这里需要指出的是,依靠继承是不够的。开发人员必须通过将 XmlInclude 属性附加到基类,或者通过将 XmlElement 属性附加到字段(这些字段可以容纳从所声明的类型派生的类型的对象),来声明 XmlSerializer 的派生类型。

例如,请看一下以下类层次结构:

public class Base
{
   public string Field;
}
public class Derived
{
  public string AnotherField;
}
public class Container
{
  public Base MyField;
}
如果您依赖继承并且编写了与下面类似的序列化代码:
Container obj = new Container();
obj.MyField = new Derived(); // legal assignment in the 
                             //.NET type system
// ...
XmlSerializer serializer = new XmlSerializer( typeof( Container ) );
serializer.Serialize( writer, obj ); // Kaboom!

您将得到发自 Serialize() 方法的异常,这是因为没有 XmlSerializer 的显式类型声明。

发自 XmlSerializer 的异常

诊断这些问题的根源在开始时可能比较困难,这是因为来自 XmlSerializer 的异常看起来并没有提供有关其产生原因的大量信息;至少,它们没有在开发人员通常会查看的地点提供信息。

在大多数情况下,当发生错误时,Serialize、Deserialize 甚至 XmlSerializer 构造函数都会引发一个相当普通的 System.InvalidOperationException。该异常类型可以在 .NET 框架中的许多地方出现;它根本不是 XmlSerializer 所特有的。更糟糕的是,该异常的 Message 属性也仅产生非常普通的信息。在上述示例中,Serialize() 方法会引发带有以下消息的异常:

There was an error generating the XML document.

该消息最多也就是令��讨厌的,因为当您看到 XmlSerializer 引发异常时,就已经猜到了这一点。现在,您只好无奈地发现该异常的 Message 无法帮助您解决问题。

奇怪的异常消息和非描述性的异常类型反映了本文前面介绍的 XmlSerializer 内部工作方式。Serialize() 方法会捕获序列化类中引发的所有异常,将它们包装到 InvalidOperationException 中,然后将该异常包沿着堆栈向上传递。

读取异常消息

得到“实际”的异常信息的窍门是检查该异常的 InnerException 属性。InnerException 引用了从序列化类内部引发的实际异常。它包含有关该问题及其发生地点的非常详细的信息。您在运行上述示例时捕获的异常将包含带有以下消息的 InnerException:

The type Derived was not expected. Use the XmlInclude or SoapInclude 
attribute to specify types that are not known statically.

您可以通过直接检查 InnerException 或者通过调用该异常的 ToString() 方法来得到此消息。下面的代码片段演示了一个异常处理程序,它写出了在反序列化对象的过程中发生的所有异常中的信息:

public void SerializeContainer( XmlWriter writer, Container obj )
{
  try
  {
    // Make sure even the construsctor runs inside a
    // try-catch block
    XmlSerializer ser = new XmlSerializer( typeof(Container));
    ser.Serialize( writer, obj );
  }
  catch( Exception ex )               
  {                                   
    DumpException( ex );             
  }                                   
}
public static void DumpException( Exception ex )
{
  Console.WriteLine( "--------- Outer Exception Data ---------" );        
  WriteExceptionInfo( ex );
  ex = ex.InnerException;                     
  if( null != ex )               
  {                                   
    Console.WriteLine( "--------- Inner Exception Data ---------" );                
    WriteExceptionInfo( ex.InnerException );    
    ex = ex.InnerException;
  }
}
public static void WriteExceptionInfo( Exception ex )
{
  Console.WriteLine( "Message: {0}", ex.Message );                  
  Console.WriteLine( "Exception Type: {0}", ex.GetType().FullName );
  Console.WriteLine( "Source: {0}", ex.Source );                    
  Console.WriteLine( "StrackTrace: {0}", ex.StackTrace );           
  Console.WriteLine( "TargetSite: {0}", ex.TargetSite );            
}

声明序列化类型

要解决上述示例中的问题,您只需读取 InnerException 的消息并实现建议的解决方案。传递给 Serialize 方法的对象图中的一个字段引用了一个类型为 Derived 的对象,但并未将该字段声明为序列化 Derived 类型的对象。尽管该对象图在 .NET 类型系统中完全合法,但 XmlSerializer 的构造函数在遍历容器类型的字段时,并不知道为 Derived 类型的对象创建了序列化代码,这是因为它没有找到对 Derived 类型的引用。

要向 XmlSerializer 声明其他字段和属性类型,您拥有多种选择。您可以通过 XmlInclude 属性(由异常消息提示)声明基类上的派生类型,如下所示:

[System.Xml.Serialization.XmlInclude( typeof( Derived ) )]
public class Base
{
    // ...
}

通过附加 XmlInclude 属性,可以让 XmlSerializer 在字段或属性被定义为 Base 类型时序列化引用 Derived 类型对象的字段。

或者,您还可以仅在单个字段或属性上声明有效类型,而不是在基类上声明派生类型。您可以将 XmlElement、XmlAttribute 或 XmlArrayItem 属性附加到字段,并且声明该字段或属性可以引用的类型。然后,XmlSerializer 的构造函数会将序列化和反序列化这些类型所需的代码添加到序列化类中。

读取 StackTrace

InnerException 的 Message 属性并不是唯一包含有价值信息的属性。StackTrace 属性传达了更多有关错误根源的详细信息。在堆栈跟踪的最顶端,您可以找到首先引发异常的方法的名称。临时程序集中的方法名称对于序列化类遵循格式 Write_,对于反序列化类则遵循格式 Read_。在具有上述错误命名空间的示例中,您可以看到异常源自名为 Read1_MyClass 的方法。稍后,我将向您说明如何使用 Visual Studio 调试器设置断点并单步执行此方法。不过,首先让我们看一下围绕反序列化 XML 文档发生的常见问题。

反序列化 XML 时发生的问题

将 XML 文档反序列化为对象图不像将对象图序列化为 XML 那样容易出错。当对象不十分匹配类型定义时,XmlSerializer 会非常敏感,但如果反序列化的 XML 文档不十分匹配对象,则它会非常宽容。对于与反序列化对象中的字段或属性不对应的 XML 元素,XmlSerializer 不再引发异常,而只是简单地引发事件。如果您需要跟踪反序列化的 XML 文档与 XML 格式之间的匹配程度,则可以注册这些事件的处理程序。然而,您不需要向 XmlSerializer 注册事件处理程序以正确处理未映射的 XML 节点。

在反序列化过程中,只有几种错误条件会导致异常。最常见的条件有:

  • 根元素的名称或其命名空间不匹配期望的名称。

  • 枚举数据类型呈现未定义的值。

  • 文档包含非法 XML。

就像序列化的情形一样,每当发生问题时,Deserialize() 方法都会引发带有以下消息的 InvalidOperation 异常

There is an error in XML document (, ).

该异常通常在 InnerException 属性中包含真正的异常。InnerException 的类型随读取 XML 文档时发生的实际错误而有所不同。如果序列化程序无法用传递给构造函数的类型、通过 XmlInclude 属性指定的类型或者在传递给 XmlSerializer 构造函数的某个更为复杂的重载的 Type[] 中指定的类型来匹配文档的根元素,则 InnerException 为 InvalidCastException。请记住,XmlSerializer 将查看 Qname(即元素的名称)和命名空间,以确定要将文档反序列化为哪个类。它们都必须匹配 .NET 类中的声明,以便 XmlSerializer 正确标识与文档的根元素相对应的类型。

让我们看一个示例:

[XmlRoot( Namespace="urn:my-namespace" )]
public class MyClass
{
  public string MyField;
}

反序列化以下 XML 文档将导致异常,因为 MyClass 元素的 XML 命名空间并不像通过 .NET 类上的 XmlRoot 属性所声明的那样是 urn:my-namespace。

<MyClass> <MyField>Hello, World</MyField> </MyClass>

让我们更进一步地观察一下该异常。异常 Message 比您从 Serialize() 方法中捕获的消息更具描述性;至少它引用了文档中导致 Deserialize() 失败的位置。尽管如此,当您处理大型 XML 文档时,查看文档并确定错误可能不会如此简单。InnerException 又一次提供了更好的信息。这一次,它显示:

<MyClass xmlns=''> was not expected.

该消息仍然有一些模糊,但它的确向您指明了导致问题的元素。您可以回头仔细检查一下 MyClass 类,并将元素名称和 XML 命名空间与 .NET 类中的 XML 序列化属性进行比较。

反序列化无效的 XML

另一个经常报告的问题是无法反序列化无效的 XML 文档。XML 规范禁止在 XML 文档中使用某些控制字符。然而,有时您仍然会收到包含这些字符的 XML 文档。正如您猜想的那样,问题暴露在 InvalidOperationException 中。尽管如此,在这种特殊情况下,InnerException 的类型是 XmlException。InnerException 的消息正中要害:

hexadecimal value <value>, is an invalid character

如果您通过将其 Normalization 属性设置为 true 的 XmlTextReader 进行反序列化,则可以避免此问题。遗憾的是,ASP.NET Web 服务在内部使用的 XmlTextReader 将其 Normalization 属性设置为 false;也就是说,它将不会反序列化包含这些无效字符的 SOAP 消息。

来自构造函数的异常

本文讨论的最后一类问题发生在 XmlSerializer 的构造函数对传入的类型进行分析的时候。请记住,构造函数将递归检查类型层次结构中的每个公共字段和属性,以便创建用来处理序列化和反序列化的类。然后,它将即时编译这些类,并加载得到的程序集。

在这一复杂的过程中,可能会发生许多不同的问题:

  • 根元素的声明类型或者由属性或字段引用的类型不提供默认的构造函数。

  • 层次结构中的某个类型实现了集合接口 Idictionary

  • 执行对象图中某个类型的构造函数或属性访问器时,需要提升安全权限。

  • 生成的序列化类的代码无法编译。

试图向 XmlSerializer 构造函数传递不可序列化的类型也会导致 InvalidOperationException,但这一次该异常不会包装其他异常。Message 属性包含对构造函数拒绝传入“类型”的原因的充分解释。试图序列化未实现不带参数的构造函数(默认构造函数)的类的实例时,将产生带有以下 Message 的异常:

Test.NonSerializable cannot be serialized because it does not have a default public constructor.

另一方面,解决编译错误是非常复杂的。这些问题暴露在带有以下消息的 FileNotFoundException 中:

File or assembly name abcdef.dll, or one of its dependencies, was not found. File name: "abcdef.dll"
   at System.Reflection.Assembly.nLoad( ... )
   at System.Reflection.Assembly.InternalLoad( ... )
   at System.Reflection.Assembly.Load(...)
   at System.CodeDom.Compiler.CompilerResults.get_CompiledAssembly() 
    ....

您可能不知道“找不到文件”异常与实例化序列化程序对象之间有什么关系,但请记住:构造函数写入 C# 文件并试图编译这些文件。该异常的调用堆栈提供了一些有用的信息,为这种怀疑提供了依据。当 XmlSerializer 试图加载由调用 System.Reflection.Assembly.Load 方法的 CodeDOM 生成的程序集时,发生了该异常。该异常没有提供有关 XmlSerializer 根据推测要创建的程序集不存在的原因的解释。通常,该程序集不存在的原因是编译失败,这是由于序列化属性生成了 C# 编译器无法编译的代码,但这种情况很少出现。

当 XmlSerializer 运行时所属的帐户或安全环境无法访问 temp 目录时,也会发生该错误。

XmlSerializer 所引发的任何异常错误消息都不包含实际的编译错误,甚至连 InnerException 也不包含实际的编译错误。这使得解决这些异常变得非常困难,直到 Chris Sells 发布了他的 XmlSerializerPrecompiler 工具。

XmlSerializerPreCompiler

XmlSerializer PreCompiler 是一个命令行程序,它执行与 XmlSerializer 的构造函数相同的步骤。它可分析类型,生成序列化类,并编译这些类 — 因为它被纯粹设计为故障排除工具,所以它可以安全地向控制台写入任何编译错误。

该工具使用起来非常方便。您只需使该工具指向包含导致异常的类型的程序集,并指定要预编译的类型。让我们看一个示例。当您将 XmlElement 或 XmlArrayItem 属性附加到定义为交错数组的字段时,会发生一个经常报告的问题,如下面的示例所示:

namespace Test
{
  public class StringArray
  {
    [XmlElement( "arrayElement", typeof( string ) )]
    public string [][] strings;
  }
}

在为类型 Test.StringArray 实例化 XmlSerializer 对象时,XmlSerializer 构造函数会引发 FileNotFoundException。如果您编译该类并试图序列化该类的实例,将得到 FileNotFoundException,但不会得到有关该问题实质的线索。XmlSerializerPreCompiler 可以为您提供缺少的信息。在我的示例中,StringArray 类被编译为名为 XmlSer.exe 的程序集,并且我必须用下面的命令行运行该工具:

XmlSerializerPreCompiler.exe XmlSer.exe Test.StringArray

第一个命令行参数指定了程序集,第二个参数定义了该程序集中要预编译的类。该工具会向命令窗口写入大量信息。

stringarray

2. XmlSerializerPreCompiler 命令窗口输出

需要查看的重要代码行是具有编译错误的代码行以及两个与以下内容类似的代码行:

XmlSerializer-produced source:
C:\DOCUME~1\\LOCALS~1\Temp\.cs

现在,XmlSerializerPreCompiler 为我们提供了编译错误以及含有无法编译的代码的源文件的位置。

调试序列化代码

通常情况下,XmlSerializer 会在不再需要序列化类的 C# 源文件时将其删除。然而,有一个未经证实的诊断开关,可用来指示 XmlSerializer 将这些文件保留在硬盘上。您可以在应用程序的 .config 文件中设置此开关:

<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.diagnostics> <switches> <add name="XmlSerialization.Compilation" value="4" /> </switches> </system.diagnostics> </configuration>

若此开关出现在 .config 文件中,C# 源文件将保留在 temp 目录中。如果您使用的计算机运行 Windows 2000 或更高版本,则 temp 目录的默认位置是 \Documents and Settings\\LocalSettings\Temp 或 \Temp(对于在 ASP.NET 帐户下运行的 Web 应用程序)。这些 C# 文件很容易丢失,因为它们的文件名看起来非常奇怪并且是随机生成的,例如:bdz6lq-t.0.cs。XmlSerializerPreCompiler 可设置该诊断开关,因此您可以在记事本或 Visual Studio 中打开这些文件,以检查 XmlSerializerPreCompiler 对其报告编译错误的代码行。

您甚至可以逐句执行这些临时序列化类,因为诊断开关还可以将含有调试符号的 .pdb 文件保留在硬盘上。如果您需要在序列化类中设置断点,则可以在 Visual Studio 调试器下运行应用程序。一旦您在输出窗口中看到相关消息,表明应用程序已经从 temp 目录中加载了具有这些奇特名称的程序集,就可以打开具有相应名称的 C# 文件,然后像在您自��的代码中一样设置断点。

assemblyloaded

3. 来自诊断开关的编译错误输出

在序列化类中设置断点之后,您需要执行代码以调用 XmlSerializer 对象上的 Serialize() 或 Deserialize() 方法。

您只能调试序列化和反序列化,而不能调试在构造函数中运行的代码生成过程。

通过在序列化类中单步执行,您能够查明每个序列化问题。如果您要单步执行 SOAP 消息的反序列化,则可以使用上述技巧,这是因为 ASP.NET Web 服务和 Web 服务代理是在 XmlSerializer 之上构建的。您需要做的只是将诊断开关添加到您的 config 文件中,然后在反序列化消息的类中设置断点。如果 WSDL 在生成代理类时没有准确地反映消息格式,则我偶尔会使用上述技巧来判断正确的序列化属性集。

小结

这些提示应该可以帮助您诊断 XmlSerializer 中的序列化问题。您遇到的大多数问题都源自 XML 序列化属性的错误组合,或源自与要反序列化的类型不匹配的 XML。序列化属性控制序列化类的代码生成,并且可能导致编译错误或运行时异常。通过仔细地检查由 XmlSerializer 引发的异常,可帮助您识别运行时异常的根源。如果您需要进一步诊断问题,则可以使用 XmlSerializerPreCompiler 工具来帮助您查找编译错误。如果任一种方法都不能帮助您找到问题的根源,则可以检查自动创建的序列化类的代码,并在调试器中逐句执行这些代码。

致谢

在此感谢 Dare ObasanjoDaniel Cazzulino 对本文提供了反馈和编辑建议。

 

posted @ 2010-08-09 15:16 懒人ABC 阅读(83) 评论(3) 编辑

参考

 

本来想按照 sos 的帮助文件上命令的分类逐步介绍 WinDbg 下使用 sos 调试 CLR 程序,但发现这样实在不够直观。索性改成根据我分析 CLR 的实际案例,step by step 介绍功能,这样结构上虽然混乱一点,但更加直观,也易于上手 :P

前面两篇文章里面分别介绍了 WinDbg 的调试配置和线程的基本概念,这篇文章将针对 JIT 编译对象方法的流程进行分析,逐步介绍如何使用 WinDbg 调试 CLR 程序。

用WinDbg探索CLR世界 [1] - 安装与环境配置
用WinDbg探索CLR世界 [2] - 线程

首先写一个简单的例子程序 demo.cs 并编译为 demo.exe,使用配置好的 WinDbg 打开之:


以下为引用:

using System;

namespace flier
{
class EntryPoint
{
public void m1()
{
System.Console.Write("EntryPoint.m1()");
}

public void m2()
{
System.Console.Write("EntryPoint.m2()");
}

public static void Main()
{
EntryPoint ep = new EntryPoint();

ep.m1();
ep.m2();
}
}
}




WinDbg 会在载入 demo.exe 后中断执行。此时可以使用 .load sos 命令加载 sos.dll 命令扩展,并用 .chain 验证加载是否成功;然后用 ld demo 命令加载 demo.exe 的调试符号文件,用 lm 命令验证加载是否成功。
然后用 ld kernel32 加载 Kernel32 的调试符号文件,并用 bp kernel32!LoadLibraryExW "du poi(esp+4)" 命令在载入 DLL 的函数入口加上断点。接下来就是一路 g 指令,直到 mscorwks.dll 被加载。这个 mscorwks.dll 就是类似 JVM 中 jvm.dll 的虚拟机实现代码,我们要了解的大部分功能都在其中。详细的解释可以参看我以前的一篇文章《.Net平台下CLR程序载入原理分析》

在 mscorwks.dll 被载入后用 ld mscorwks 命令载入其调试符号库,就可以正式开始我们的探索工作了 :D

目前使用到的 WinDbg 命令如下



以下为引用:

.load sos // 加载 sos 调试扩展模块,可使用 .chain 命令验证

ld demo // 加载 demo.exe 调试符号库,可使用 lm 命令验证

ld kernel32 // 加载 kernel32.exe 调试符号库

bp kernel32!LoadLibraryExW "du poi(esp+4)" // 设置断点监视何时 mscorwks.dll 被载入

g // 执行直到 mscorwks.dll被加载

bd 0 // 清除前面设置的断点,开始对 mscorwks.dll 进行处理

ld mscorwks // 加载 mscorwks.dll 调试符号库





Don Box 在《.NET本质论 第1卷:公共语言运行库》的第六章介绍了方法调用的内部实现流程。其中提到方法表在 JIT 之前,保存的都是 call mscorwks.dll!PreStubWorker 调用,直到第一次使用时,才会对目标 IL 代码进行 JIT 编译,并调用之。因此我们第一步可以在此函数上设置断点(bp mscorwks!PreStubWorker),看看系统是如何调用此函数的。


以下为引用:

0:000> bp mscorwks!PreStubWorker
0:000> g
ModLoad: 70ad0000 70bb6000 E:\WINDOWS\WinSxS\x86_Microsoft.Windows.Common-Controls_6595b64144ccf1df_6.0.100.0_x-ww_8417450B\comctl32.dll
ModLoad: 79780000 79980000 e:\windows\microsoft.net\framework\v1.1.4322\mscorlib.dll
ModLoad: 79980000 79ca6000 e:\windows\assembly\nativeimages1_v1.1.4322\mscorlib\1.0.5000.0__b77a5c561934e089_ed6bc96c\mscorlib.dll
ModLoad: 79510000 79523000 E:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\mscorsn.dll
Breakpoint 1 hit
eax=0012f7c0 ebx=00148c60 ecx=04aa112c edx=00000004 esi=0012f784 edi=0012f9a8
eip=791d6a4a esp=0012f764 ebp=0012f79c iopl=0 nv up ei pl zr na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=0038 gs=0000 efl=00000246
mscorwks!PreStubWorker:
791d6a4a 55 push ebp




断点被激活就代表函数被调用。我们先使用 k 看看函数被调用时的上下文环境。


以下为引用:

0:000> k
ChildEBP RetAddr
0012f760 0014930e mscorwks!PreStubWorker
WARNING: Frame IP not in any known module. Following frames may be wrong.
0012f79c 791da434 0x14930e
0012f8b4 791dd2ec mscorwks!MethodDesc::CallDescr+0x1b6
0012f96c 79240405 mscorwks!MethodDesc::Call+0xc5
0012fa18 79240520 mscorwks!AppDomain::InitializeDomainContext+0x10f
0012fa7c 7923d744 mscorwks!SystemDomain::InitializeDefaultDomain+0x11c
0012fd60 791c6e73 mscorwks!SystemDomain::ExecuteMainMethod+0x120
0012ffa0 791c6ef3 mscorwks!ExecuteEXE+0x1c0
0012ffb0 7880a53e mscorwks!_CorExeMain+0x59
0012ffc0 77e1f38c mscoree!_CorExeMain+0x30 [f:\dd\ndp\clr\src\dlls\shim\shim.cpp @ 5426]
0012fff0 00000000 KERNEL32!BaseProcessStart+0x23




这里可以看到从 mscoree!_CorExeMain 一路执行下来的步骤,而那个警告说明这个 stack frame 不在任意一个已知模块中。这是很正常的,因为这个栈帧实际上是指向由 JIT 动态生成的代码。我们监视的 mscorwks!PreStubWorker 函数只是作为方法表中函数的入口 stub,系统启动时还会通过其他方式调用 JIT 完成代码的编译执行。
接下来用 SOS 的 !clrstack 命令看看 CLR 的调用堆栈,显示如下:


以下为引用:

0:000> !clrstack
succeeded
Loaded Son of Strike data table version 5 from "E:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\mscorwks.dll"
Thread 0
ESP EIP
0012f784 791d6a4a [FRAME: PrestubMethodFrame] [DEFAULT] [hasThis] Void System.AppDomain.SetupDomain(ValueClass System.LoaderOptimization,String,String)
0012f9a8 791d6a4a [FRAME: GCFrame]
0012fad0 791d6a4a [FRAME: DebuggerClassInitMarkFrame]
0012fa94 791d6a4a [FRAME: GCFrame]




如果需要更为详细的详细,可以使用 -p, -l 或 -r 参数分别显示参数、局部变量和寄存器,当然前两者需要调试符号库的支持才行。

如此一路 g; !clrstack 执行下去,直到 flier.EntryPoint.m1 函数需要被处理为止:


以下为引用:

0:000> !clrstack
Thread 0
ESP EIP
0012f68c 791d6a4a [FRAME: PrestubMethodFrame] [DEFAULT] [hasThis] Void flier.EntryPoint.m1()
0012f69c 06d90080 [DEFAULT] Void flier.EntryPoint.Main()
0012f9b0 791da717 [FRAME: GCFrame]
0012fa94 791da717 [FRAME: GCFrame]




此时用 !dumpstackobjects 命令可以查看当前线程堆栈中使用的所有对象


以下为引用:

0:000> !dumpstackobjects
ESP/REG Object Name
ecx 04aa1a90 flier.EntryPoint
0012f678 04aa1a90 flier.EntryPoint
0012f67c 04aa1a90 flier.EntryPoint
0012f680 04aa1a90 flier.EntryPoint




这里的 flier.EntryPoint 对象地址 0x04aa1a90 就是我们要分析的对象在内存中的位置。

这一阶段使用到的 WinDbg 命令如下:


以下为引用:

bp mscorwks!PreStubWorker // 设置代码断点

g // 继续运行至断点

k // 查看函数调用时的 Native 堆栈调用

!clrstack // 查看函数调用时的 CLR 堆栈调用

!dumpstackobjects // 查看线程堆栈中使用到的所有对象





知道地址后,就可以用 !dumpobj 命令查看对象的详细信息


以下为引用:

0:000> !dumpobj 04aa1a90
Name: flier.EntryPoint
MethodTable 0x009750a8
EEClass 0x06c632e8
Size 12(0xc) bytes
mdToken: 02000002 (D:\Temp\demo.exe)




信息包括对象的类型名字(Name)和类型信息的地址(EEClass),以及对象的大小(Size)和 Token (mdToken),而方法表 (MethodTable) 正是我们分析方法调用的目标。我们可以用 !dumpclass 命令先进一步查看对象的类型信息:


以下为引用:

0:000> !dumpclass 0x6c632e8
Class Name : flier.EntryPoint
mdToken : 02000002 ()
Parent Class : 79b7c3c8
ClassLoader : 00153850
Method Table : 009750a8
Vtable Slots : 4
Total Method Slots : 8
Class Attributes : 100000 :
Flags : 1000003
NumInstanceFields: 0
NumStaticFields: 0
ThreadStaticOffset: 0
ThreadStaticsSize: 0
ContextStaticOffset: 0
ContextStaticsSize: 0




可以发现其信息与对象信息有很多符合之处,正如 Don Box 所说,一个对象引用指向一个类型 EEClass 实例,而方法表为类型所有,其对象共有。我们可以使用 !dumpmt 命令进一步查看方法表的信息,-md 参数表示需要查看每个方法描述 (MethodDesc):


以下为引用:

0:000> !dumpmt -md 0x09750a8
EEClass : 06c632e8
Module : 0014e090
Name: flier.EntryPoint
mdToken: 02000002 (D:\Temp\demo.exe)
MethodTable Flags : 80000
Number of IFaces in IFaceMap : 0
Interface Map : 009750f4
Slots in VTable : 8
--------------------------------------
MethodDesc Table
Entry MethodDesc JIT Name
79b7c4eb 79b7c4f0 None [DEFAULT] [hasThis] String System.Object.ToString()
79b7c473 79b7c478 None [DEFAULT] [hasThis] Boolean System.Object.Equals(Object)
79b7c48b 79b7c490 None [DEFAULT] [hasThis] I4 System.Object.GetHashCode()
79b7c52b 79b7c530 None [DEFAULT] [hasThis] Void System.Object.Finalize()
0097506b 00975070 None [DEFAULT] [hasThis] Void flier.EntryPoint.m1()
0097507b 00975080 None [DEFAULT] [hasThis] Void flier.EntryPoint.m2()
0097508b 00975090 None [DEFAULT] Void flier.EntryPoint.Main()
0097509b 009750a0 None [DEFAULT] [hasThis] Void flier.EntryPoint..ctor()




可以看到方法表中共有8个表项,其中前4个已经绑定到使用 ngen 预编译好的静态函数上


以下为引用:

0:000> u 79b7c4eb
mscorlib_79980000+0x1fc4eb:
79b7c4eb e8909cfeff call mscorlib_79980000+0x1e6180 (79b66180)
79b7c4f0 0000 add [eax],al
79b7c4f2 0080d86206c0 add [eax+0xc00662d8],al
79b7c4f8 06 push es
79b7c4f9 00fc add ah,bh
79b7c4fb e8809cfeff call mscorlib_79980000+0x1e6180 (79b66180)
79b7c500 07 pop es
79b7c501 0010 add [eax],dl




后四个则作为可被覆盖的虚方法在方法表中,这也是为什么在查看类型信息时 Vtable Slots = 4 而 Total Method Slots = 8 的原因。

对方法表的每个项目,可以用 !DumpMD 命令查看详细描述,如


以下为引用:

0:000> !DumpMD 0x00975070
Method Name : [DEFAULT] [hasThis] Void flier.EntryPoint.m1()
MethodTable 9750a8
Module: 14e090
mdToken: 06000001 (D:\Temp\demo.exe)
Flags : 0
IL RVA : 00002050




IL RVA 说明此方法的 IL 代码相对虚拟地址(IL RVA),也就是说此方法还没有被 JIT,仍以 IL 代码形式存在。对于已经完成 JIT 的方法,将显示其 JIT 后函数体代码的虚拟地址(Method VA):


以下为引用:

0:000> !DumpMD 0x009750a0
Method Name : [DEFAULT] [hasThis] Void flier.EntryPoint..ctor()
MethodTable 9750a8
Module: 14e090
mdToken: 06000004 (D:\Temp\demo.exe)
Flags : 0
Method VA : 06d900a8





这一阶段使用到的 WinDbg 命令如下:


以下为引用:

!dumpobj 04aa1a90 // 查看对象的详细信息

!dumpclass 0x6c632e8 // 查看类型的详细信息

!dumpmt -md 0x09750a8 // 查看方法表的详细信息

!dumpmd 0x00975070 // 查看方法表项的方法描述的详细信息

u 0x79b7c4eb // 反汇编指定地址的指令





我们反汇编一下 !DumpMT 命令列出的几个方法,就会发现正如 Don Box 所说,已经被 JIT 的代码指向一个jmp指令,直接跳转到编译后的方法体,如:


以下为引用:

0:000> u 0097509b
0097509b e908b04106 jmp 06d900a8




而没有被 JIT 的函数,则指向一个call指令,调用一个 prolog 代码,间接调用 mscorwks!PreStubWorker 函数完成实际 JIT 工作,如:


以下为引用:

0:000> u 0x0097506b
0097506b e878427dff call 001492e8

0:000> u 0x0097507b
0097507b e868427dff call 001492e8




这个 prolog 代码很简单,负责构造 mscorwks!PreStubWorker 所需的调用堆栈


以下为引用:

0:000> u 0x001492e8
001492e8 52 push edx
001492e9 68f0301b79 push 0x791b30f0
001492ee 55 push ebp
001492ef 53 push ebx
001492f0 56 push esi
001492f1 57 push edi
001492f2 8d742410 lea esi,[esp+0x10]
001492f6 51 push ecx
001492f7 52 push edx
001492f8 648b1d2c0e0000 mov ebx,fs:[00000e2c]
001492ff 8b7b08 mov edi,[ebx+0x8]
00149302 897e04 mov [esi+0x4],edi
00149305 897308 mov [ebx+0x8],esi
00149308 56 push esi
00149309 e83cd70879 call mscorwks!PreStubWorker (791d6a4a)
0014930e 897b08 mov [ebx+0x8],edi
00149311 894604 mov [esi+0x4],eax
00149314 5a pop edx
00149315 59 pop ecx
00149316 5f pop edi
00149317 5e pop esi
00149318 5b pop ebx
00149319 5d pop ebp
0014931a 83c404 add esp,0x4
0014931d 8f0424 pop [esp]
00149320 c3 ret




而这段 prolog 代码是由类似 ROTOR 中的 GeneratePrestub 函数(vm\i386\cgenx86.cpp:1829) 动态生成的,完成对 PreStubWorker 函数调用的封装。而 PreStubWorker 函数会调用 JIT 完成真正的函数编译工作,并将方法表的入口改为指向编译后函数体的 jmp 指令。具体的流程请参考Don Box 在《.NET本质论 第1卷:公共语言运行库》的第六章中的介绍,这里就不再罗嗦了。以后有机会再写篇文章详细分析一下 JIT 的工作流程。

在 JIT 处理 flier.EntryPoint.m1 时,用 g 命令执行,再回头来分析 m1 函数的入口,就会发现如前面所述,调用 JIT 过程的 call 指令变成了直接调用 Native 函数体的 jmp 指令。:D


这一小节,我们介绍了使用 WinDbg 跟踪调试 CLR 程序的一遍流程,并了解了对堆栈、对象和类信息进行分析的 SOS 命令,希望大家能够借此开始探索 CLR 内部世界的旅程。 :P

Jason Zander在其 BLog 的一篇文章,SOS Debugging with the CLR (Part 1),里面也详细介绍了使用 WinDbg 和 SOS 调试 CLR 程序的部分方法,值得一看。

相关文章:
  • 用WinDbg探索CLR世界 [4] 方法的调用机制
  • 用WinDbg探索CLR世界 [3] 跟踪方法的 JIT 过程
  • 用WinDbg探索CLR世界 [2] 线程
  • 用WinDbg探索CLR世界[1] - 安装与环境配置
  •  

    posted @ 2010-08-09 15:05 懒人ABC 阅读(60) 评论(0) 编辑

    2010年8月7日

    1)使用System.Net.NetworkInformation.NetworkInterface.GetIsNetworkAvailable()判断联网状态

    注意:只要有一个网络是通的方法就返回true,其解释是Indicates whether any network connection is available.

    System.Net.NetworkInformation命名空间下另有NetworkChange类,此类提供了网络连接状态改变事件(注意也是any network conn)和网络地址改变事件(针对any network conn的ip改变),用起来还是很方便的。

    2)使用Microsoft.VisualBasic.Devices.Computer.Network.IsAvailable属性

    注意:需要引用Microsoft.VisualBasic.dll,这个是VB在.net下的实现,里面有很多方便的小功能哦,放心它也是代码安全的。

    这里也提供了连接更改事件(也是any network conn的)。

    总结:最后的两个推荐方案简单实用高效,推荐使用;

    posted @ 2010-08-07 14:28 懒人ABC 阅读(81) 评论(0) 编辑

    2010年6月29日

    (使用Windows Service作为宿主的时候也会出现这样的情况,搜索的) 我们这里是自定义托管宿主,在进行WCF编程开发过程时,使用NetTcpBinding绑定协议,作为通讯协议,可能会引发这样的异常,导致数据如法传输。套接字连接中断,可能是由于消息处理错误,或者远程宿主接受超时引起,或者是底层网络资源问题导致,本地套接字时间是'00:00:59.7656250'。具体信息如下:

      The socket connection was aborted. This could be caused by an error processing your message or a receive timeout being exceeded by the remote host, or an underlying network resource issue. Local socket timeout was '00:00:59.7656250'.

       此问题的的解决办法:

       我查询了很多资料,国外论坛也有人已经遇到这样的问题。

      1.http://social.msdn.microsoft.com/forums/en-US/wcf/thread/637e6097-9161-40ee-8578-46388b7647cd

      2.http://social.msdn.microsoft.com/forums/en-US/wcf/thread/06cb1522-31f0-4ce3-85f0-02656228a8e1/

      这个是两个MSDN上讨论的解决办法,更换其他的绑定协议确实可以解决问题。

      参考代码如下:

    WSHttpBinding binding = new WSHttpBinding();
    binding.ReceiveTimeout = new TimeSpan(10, 10, 10);

      我测试的结果使用basicHttpBinding协议也可以,不会出现这样的异常。

      如果大家有好的经验,也欢迎补充。

      系列文章:

      WCF分布式开发常见错误解决(1):An error occurred while attempting to find services at...添加服务引用出错

      WCF分布式开发常见错误解决(2)无法从传输连接中读取数据: 远程主机强迫关闭了一个现有的连接

      WCF分布式开发常见错误解决(3):客户端调用服务出错
      WCF分布式开发常见错误解决(4):The type or namespace name 'DataContract' could not be found DataContract找不到

      WCF分布式开发常见错误解决(5)Could not find a base address that matches scheme

      WCF分布式开发常见错误解决(6)Service 'WcfServiceApp.WCFService' has zero application

      WCF分布式开发常见错误解决(7):System.InvalidOperationException,Cannot have two operations in the same contract

      WCF分布式开发常见错误解决(8):不一致的访问性Inconsistent accessibility
      WCF分布式开发常见错误解决(9):无终结点监听,There was no endpoint listening at

      WCF分布式开发常见错误解决(11):There is already a listener on IP endpoint ,IP 终结点 已经存在侦听器

      WCF分布式开发常见错误解决(12):The server was unable to process the request,服务无法处理请求

      WCF分布式开发常见错误(13):The transaction under which this method call was executing ,此方法调用的事务被异步中断

      WCF分布式开发常见错误(14):无效的操作异常,At least one operation on the ...

      WCF分布式开发常见错误(15):Communication with the underlying transaction manager has failed

      WCF分布式开发常见错误(16):The Transaction has aborted,事务已经被中断

      WCF分布式开发常见错误(17):无法启动MSMQ服务

    本篇文章来自<A href='http://www.soidc.net'>IDC专家网</a> 原文链接:http://www.soidc.net/articles/1213781277395/20090615/1215945539001_1.html

     

    具体参考

    http://www.soidc.net/topic/%B7%D6%B2%BC%CA%BD%CD%A8%D1%B6%B4%ED%CE%F3

    posted @ 2010-06-29 19:53 懒人ABC 阅读(74) 评论(0) 编辑

    2010年5月20日

    摘要: #!/usr/bin/python# -*- coding: utf-8 -*-# Filename : helloworld.pyprint u'使用单引号定义字符串'print u"使用双引号定义字符串"print u'''使用三引号创建换行第二行第三行'''print u'使用转义符\''print u'使用行末\来连接太长的一行字符串\过长部分字符串'print ur'使用r创建自然字符串...阅读全文

    posted @ 2010-05-20 17:54 懒人ABC 阅读(88) 评论(0) 编辑

    2010年5月19日

    摘要: 随着IronPyhon 2.0 的发布,.NET Dynamic Language Runtime 也更加成熟了,在2.0中我们可以用动态脚本以粘合剂的方式编写架构体系中的各种逻辑单元,既便于修改,又能灵活适合多变的业务场景。当然,我的目标是在 Platform Framework 中能嵌入脚本引擎,而不是用 ipy.exe 去执行一个 “独立” 的任务。要让.net 项目...阅读全文

    posted @ 2010-05-19 17:20 懒人ABC 阅读(26) 评论(0) 编辑

    2010年5月14日

    摘要: 在c#中默认可以讲bitmap保存为gif等格式,但是这种保存方法保存的gif会严重失真   正常情况下的代码: 1 System.Drawing.Bitmap b = new System.Drawing.Bitmap(“c:\original_image.gif“); 2   System.Drawing.Image thmbnail = b.GetThumbnailI...阅读全文

    posted @ 2010-05-14 16:07 懒人ABC 阅读(237) 评论(3) 编辑

    摘要: 之前做了一个程序,需要通过web服务器生成Excel,并且往Excel里添加图片。但是一执行到添加图片的方法,线程就自动中断了。没有任何报错信息。(20kb左右的图片可以插入,大于这个左右的的就出问题)但是在winform里就是好的。后来经过高人指点,使用了advapi32.dll里的LogonUser 方法,打开的Excel进程变为了administrator,这样终于可以正常往Excel插入图...阅读全文

    posted @ 2010-05-14 15:45 懒人ABC 阅读(97) 评论(0) 编辑