乱世虾
随笔- 3  文章- 0  评论- 10 
博客园  首页  新随笔  联系  管理  订阅 订阅
2011年7月28日
无处不在的.NET:在Matlab中使用WPF

最近园子里在讨论.NET程序员努力提高自身素质的问题。园子是.NET爱好者的大本营,大家都对这门技术充满了热情。然而不能不说的是,在国内说到.NET,80%以上的语境恐怕是指ASP.NET,这无形中助长了非.NET程序员,尤其是C++程序员和C++出身的技术主管对.NET的偏见和藐视,因为C++的领域大多看上去比做网站“更有技术性”。

国内某C++出身的主管认为同样的逻辑用C++实现就会“更加底层”,用.NET就会被微软牵着鼻子走。不禁要问用C++难道能脱离调用Win32 API不成。他反复强调C++的可移植性,认为Mono远远不够成熟。他竟以为他们基于VC开发的东西只要稍作修改就可以运行于iPhone之上。

 

事实上.NET无论是用于科学研究还是用于生产,肯定都比C++有优势。今天我们要看的例子与科学研究有关。在21世纪的第一个10年过去后,国内院校和科研院所仍然流行着VB6。然而在国外,JVM和CLR平台,甚至是html5,早就是教授和学生们的利器了。很多老外的软件中都集成了面向科学研究人员而非专业开发者的.NET扩展功能。本人在做本科毕业论文时就用到了3ds max中MaxScript脚本调用.NET,用这一特性制作了Windows Forms界面来操控3ds max场景。

说到.NET平台的脚本(或者说解释型语言),大家都会想到IronPython、IronRuby之类;说到交互式命令行,再加上F#等,将来可能C#也会提供交互式的功能。这里我要说其他软件中的这些功能其实一点也不差。在Mathematica和Matlab数学软件中,均提供了对JVM和.NET的接口。借助这两个类库的强大功能,科研人员能做的事情一下子多了起来。

以Matlab为例,你可以在交互式命令行和脚本中同时使用JDK和BCL两个牛库,这等于实现了一定程度上的JVM和CLR的互通。最近对这方面兴趣大增,正在研究更多的内容。

今天我举一个Matlab中使用WPF的例子。在这个例子中,演示了Matlab中.NET对象的基本使用方法、使用Matlab函数句柄作为匿名函数作为事件处理器的方法等。希望对想给你的Matlab程序增加界面的童鞋有所帮助。

以下代码在Matlab R2010b中测试通过。

 

% load necessary assemblies for WPF

NET.addAssembly('PresentationCore');

NET.addAssembly('PresentationFramework');

NET.addAssembly('WindowsBase');

 

% show a window with a clickable button

window = System.Windows.Window;

window.Title = 'WPF in MATLAB';

window.Width = 300;

window.Height = 100;

window.WindowStartupLocation = System.Windows.WindowStartupLocation.CenterScreen; % use of enum

button = System.Windows.Controls.Button;

button.Content = 'Click me!';

addlistener(button, 'Click', @(sender, e)System.Windows.MessageBox.Show('Button clicked!')); % use of event handler

window.Content = button;

window.Show()

 

别的地方都很简单,我只解释一下“函数句柄”。这是Matlab最近几年加入的功能,很多国内教科书上都没有介绍,我也是在Matlab文档中偶然看到的,可见文档是最好的教材。函数句柄相当于C#中的匿名函数或lambda表达式这样的概念,有了它,Matlab中很多函数的使用更方便了。例如求定积分quad,以前需要把被积函数做成独立文件,然后把函数名用字符串形式传入。后来有了inline函数,但也不方便。再后来就有了函数句柄,于是我们可以直接写

quad(@sin, 0, 1)

quad(@(x)sin(x)+1, 0, 1)

等等。其中第二个就是一种lambda表达式的形式,@(x)sin(x)+1翻译成C#的lambda表达式就是x=>Math.Sin(x)+1。

针对函数句柄,Matlab现在有一些全新的函数。例如绘制函数图像,再也不需要先求两个向量了,直接可以使用fplot和ezplot。

在addlistener函数中,第一个参数是控件,第二个参数是事件名,第三个参数就是事件处理函数,以函数句柄形式传入。可以写一个独立的函数,也可以使用匿名函数(像代码中那样,@(sender, e)MessageBox.Show(…))。

 

大家不妨尝试一下:用WPF做界面,来调用java.math.BitInteger类计算大整数的幂。以下是命令行交互的结果:


>> a=java.math.BigInteger(2)

 

a =

 

2

 

>> a.pow(100)

 

ans =

 

1267650600228229401496703205376

posted @ 2011-07-28 17:38 乱世虾 阅读(1477) 评论(5) 编辑
2011年3月3日
你有过对“扩展方法”中“扩展”二字新的理解吗?

MSDN官方文档中说,“扩展方法使您能够向现有类型‘添加’方法,而无需创建新的派生类型、重新编译或以其他方式修改原始类型。扩展方法是一种特殊的静态方法,但可以像扩展类型上的实例方法一样进行调用。对于用 C# 和 Visual Basic 编写的客户端代码,调用扩展方法与调用在类型中实际定义的方法之间没有明显的差异。”

按照这种官方描述,扩展方法就是用一个静态类型去“扩展”另一个类型,使得另一个类型看起来像是被我们添加了很多“成员实例方法”。官方文档也说得很明白,“通常,您更多时候是调用扩展方法而不是实现您自己的扩展方法。”“通常,建议您只在不得已的情况下才实现扩展方法,并谨慎地实现。只要有可能,必须扩展现有类型的客户端代码都应该通过创建从现有类型派生的新类型来达到这一目的。”也就是说,扩展方法只是C#从语言上对框架功能LINQ的一种支持,语言设计者并不希望我们在其他场合使用扩展方法。

尽管如此,在特定场合下你还是会觉得有足够理由使用扩展方法。这类场合往往具有这样的特征:BCL框架类库或者第三方类库中提供了某个实例方法,但是在你的代码中使用会存在某种缺陷——1. 该方法本身没有参数有效性约束;2. 该方法是链式调用的一环,但可能返回null导致后续调用失败——而在你的代码中作出相应处理是繁琐的,会破坏可读性;并且这个方法所在类型是密封的。

这时,作为一个脑中对扩展方法概念有所了解的程序员,理应认识到扩展方法的用武之地来了,这次它不是用来扩展类型,而是,真的,“扩展”方法了。

你需要创建一个调用语法和原方法一样的扩展方法,当然,为了可读性,你的方法名称应当极具对原方法名称的某种启发性和诱导性。我们来看两个实例。

【实例1, VB】在LINQ to XML中,我们经常写下诸如
Dim s = xe.Element("E").Attribute("A").Value
这样的语句。但是如果不存在E元素或者A特性,则会返回null,导致后面抛出空引用异常。事实上我们希望遇到此情况能使s得到空字符串。为此我们定义扩展方法来做处理。以Attribute为例,我们扩展Attribute方法为AttributeX方法:

Module M
    <System.Runtime.CompilerServices.Extension()> _
    Public Function AttributeX(ByVal xe As XElement, ByVal attName As String) As XAttribute
        Return If(xe.Attribute(attName), New XAttribute(attName, String.Empty))
    End Function
End Module

类似地,我们扩展一个ElementX方法,当不存在指定元素时返回新的空元素。这样,我们即可高枕无忧地调用:

Dim s = xe.ElementX("E").AttributeX("A").Value

【实例2,C#】在AutoCAD .NET开发中,有一个Curve.GetParameterAtDistance方法,其参数范围在0到curve长度之间,否则会抛出异常。我有如下调用:

List<RoadInterval> intersectBreakIntervals = intersectDists.Select(x => new RoadInterval { start = _road.GetParameterAtDistance(x - GetHongxianWidth()), end = _road. GetParameterAtDistance (x + GetHongxianWidth()) }).ToList();

其中参数可能出现小于0或者大于curve长度的情况。由于使用了对象初始化器,不便于进行其他处理。为此我定义了扩展方法用于扩展此方法:

public static class ExtensionMethods
{
    public static double GetParamAtDist(this Curve cv, double dist)
    {
        if (dist < 0)
        {
            dist = 0;
        }
        else if (dist > cv.GetDistanceAtParameter(cv.EndParam))
        {
            dist = cv.GetDistanceAtParameter(cv.EndParam);
        }
        return cv.GetParameterAtDistance(dist);
    }
}

这样,我又高枕无忧了。

不知各位是否也悟出了“扩展”二字的新含义呢?

posted @ 2011-03-03 17:57 乱世虾 阅读(1228) 评论(2) 编辑
2011年2月14日
函数式编程实践(1):高阶函数的使用

函数式编程与我们的距离并不遥远。虽然大部分人不会选择去学习F#,但是函数式编程的思想可以用C#来实践。自从C#3.0引入了Lambda表达式,虽然是作为LINQ的配角,但是其带来的改变远远超出LINQ的范围,C#从此一只脚迈入了FP的领地。

回想Lambda在LINQ中的使用,我们就可轻易理解函数式编程的特征之一:函数作为参数传递(即“函数的函数”,函数作为自变量)。函数作为参数导致函数的复合(还记得高中数学中的f(g(x))吗)。经常用LINQ的人一定对此已经习以为常。

今天我们讨论函数式编程的另外一个特征,即高阶函数。所谓高阶函数,就是“返回函数的函数”(注意与上文“函数的函数”相区别,函数作为因变量)。举一个最简单的例子:

Func<int, Func<int, int>> f = x => y => x + y;

注意这个委托变量的声明方式,从中我们可以直接解读到,这个委托类型对应的函数是这样的:输入一个int,返回一个函数;返回的函数是这样的:输入一个int,返回一个int。

再看“f =”右边的Lambda表达式,它其实是这样:

x => (y => (x + y));

或者

x => { return y => x + y; };

这个函数就是一个返回函数的函数,即高阶函数。当我们输入外层函数的参数,例如指定1,则f(1)仍然是一个函数,它等于y => 1 + y这个函数,如果你乐意,可以给它取名叫g函数,它用于给一个数加1。 我们可以继续传入参数,例如f(1)(2),也就是g(2),也就是给2加1。

 

 

不知道这样解释是否清楚明白。也许你会说这有什么用,你永远不会用到。但是我就是这样一个人,会对不一样的体验着迷,我把这种方式用到了项目中。我的项目是城市规划应用,是一个Windows Forms实现的城市指标分析结果查看器。在项目中有如下的Paint事件处理器:

private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
    _display.g = e.Graphics;
    _display.Width = pictureBox1.Width;
    _display.Height = pictureBox1.Height;
            
    e.Graphics.Clear(Color.Black);
    if (cbShowResult.Checked)
    {
        _display.PaintValue();
    }
    if (cbShowRoads.Checked)
    {
        _display.PaintRoad();
    }
    if (cbShowParcels.Checked)
    {
        _display.PaintParcel();
    }
    if (cbShowSpot.Checked)
    {
        _display.PaintSpot();
        _display.PaintHover();
        _display.PaintSelect();
    }
    if (!string.IsNullOrEmpty(_display.ToolRuning))
    {
        _display.PaintTool();
    }
}

可以看到,在事件处理器中分别完成了绘制结果颜色、绘制道路、绘制地块、绘制点状实体、绘制点状实体悬停提示、绘制点状实体选择提示,以及绘制工具功能等模块。其中除了最后一个PaintTool,其他都是原生的静态C#函数,但PaintTool不是,它是一个Action类型的委托变量(Action委托对应不带参数、不返回值的函数)。

public Action PaintTool = () => { };

PaintTool的作用是为所有的工具进行必要的绘制,我的程序中有很多这样的工具功能,例如地块选择工具,需要根据鼠标位置高亮显示地块;测量工具,根据鼠标点选位置绘制折线;等等。如果使用传统方法,势必需要带参数的函数,例如为了高亮地块,需要传入地块;并且需要使用全局变量来为函数传入参数,因为鼠标位置的获取需要在其他事件处理器中完成。其他绘制函数都是不带参数的,那么这个难道不能统一吗?而且全局变量还是少用为好,函数式编程连变量都反对,何况全局变量。

让我优雅一次吧。我定义了如下委托变量:

public Func<CityParcel, Action> PaintHoverParcel;

在SetTool方法中为它赋值:

PaintHoverParcel = parcel => () =>
{
    if (parcel != null)
    {
        Point[] pts = parcel.Domain.Points.Select(x => CanvasCoordinate(x)).ToArray();
        g.DrawPolygon(new Pen(Color.Red, 5), pts);
    }
};

在MouseMove事件处理器中,

_display.PaintTool = _display.PaintHoverParcel(_display.DetectParcelHover(e.X, e.Y));

其中DetectParcelHover函数用于返回鼠标位置的地块,传入PaintHoverParcel中,返回正好是一个Action,于是赋值给PaintTool。

为了帮助理解,我做如下解说:为了达成不带参数的绘制地块函数,需要给每个地块做一个单独的绘制函数;但是工作量大,于是用一个函数来实现,那就是一个这样的函数:给定一个地块,返回绘制这个地块的函数。这就是我们讨论的“返回函数的函数”,即高阶函数PaintHoverParcel。

 

这是我在博客园发表的第一篇文章。写这个随笔只是想呼唤一种精神。我们会发现这样一种现象:在美国,越是从Windows API、MFC、Windows Forms、WPF一路走来的老技术人员,越是为每一次新技术带来的改变而欢呼。同样的事情到了中国,则只有无尽的守旧、抱怨、排斥甚至麻木。园子里时常对LINQ不屑的声音不在少数。我们有足够的理由相信,要想创新,首先需要对新事物有一种宽容,只有宽容对待,才能理性发现、理性领悟、理性升华、理性创造。固步自封、安于现状、畏手畏脚、盲目排新的民族,永远无法以创新者的姿态战胜那些曾经敬仰我们的人。也许,那久违的敬仰已经成为你我每一个人的包袱。

posted @ 2011-02-14 22:32 乱世虾 阅读(221) 评论(3) 编辑
仅列出标题  
Copyright ©2012 乱世虾