Author Archives: youenzeng



$ man man

The following conventions apply to the SYNOPSIS section and can be used as a guide in other sections.

bold text type exactly as shown.
italic text replace with appropriate argument.
[-abc] any or all arguments within [ ] are optional.
-a|-b options delimited by | cannot be used together.
argument … argument is repeatable.
[expression] … entire expression within [ ] is repeatable.

另外Git book里面的参数是<>扩起来的。


rethrow exception and keeps original StackTrace

Expceiton rethrow

最近在改一个项目的bug,反射调用一段代码出错,结果在Log里面看不到完整的错误. 原因是Log里面记录的是抛出的Exception,而不是内部Exception. 当我修改为抛出内部Exception时,发现记录下来Exception的StackTrace是在抛出Exception的地方,而不是引发Exception的地方, 即Exception的StackTrace被修改了.


    class Processor
        public string Runner(DateTime dateTime)
            if (dateTime.Year > 2058)
                return "Hell! It's about time!";

            throw new Exception("Hold on..");


class PluginRunner
        public void ExecuteCore()
            Assembly assembly = Assembly.LoadFrom("PluginTest.dll");

            object obj = assembly.CreateInstance("PluginTest.Processor");
            MethodInfo m = obj.GetType().GetMethod("Runner");

            if (m != null)
                object[] methodParams = new object[] { DateTime.Now };
                var result = m.Invoke(obj, methodParams);
        static void Main(string[] args)
            PluginRunner runner = new PluginRunner();
            catch (Exception ex)
                Debug.WriteLine($"exception stacktrace:" + Environment.NewLine + ex.StackTrace + Environment.NewLine);


由于调用方的异常抛出,此时, 记录异常为m.Invoke的堆栈,结果如下:

exception stacktrace:
at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)
at System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments)
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)
at Canary.PluginRunner.ExecuteCore() in xxx\Canary\PluginRunner.cs:line 23
at Canary.Program.Main(String[] args) in xxx\Canary\Program.cs:line 19


                    var result = m.Invoke(obj, methodParams);
                catch (Exception ex)
                    if (ex.InnerException != null)
                        // ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
                        throw ex.InnerException;
                    throw ex;


exception stacktrace:
   at Canary.PluginRunner.ExecuteCore() in xxx\Canary\PluginRunner.cs:line 33
   at Canary.Program.Main(String[] args) in xxx\Canary\Program.cs:line 19

但是在断点出查看时at PluginTest.Processor.Runner(DateTime dateTime),异常在抛出后被修改了.

第一种是使用.NET 4.5中引入的ExceptionDispatchInfo,如官方文档所说,这种方式会保留原始堆栈.这个class的引入应该初衷是为了配合Task调用的AggregateException.
The ExceptionDispatchInfo object stores the stack trace information and Watson information that the exception contains at the point where it is captured. The exception can be thrown at another time and possibly on another thread by calling the ExceptionDispatchInfo.Throw method. The exception is thrown as if it had flowed from the point where it was captured to the point where the Throw method is called.



exception stacktrace:
   at PluginTest.Processor.Runner(DateTime dateTime)
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at Canary.PluginRunner.ExecuteCore() in xxx\Canary\PluginRunner.cs:line 32
   at Canary.Program.Main(String[] args) in xxx\Canary\Program.cs:line 19

SO大佬在2010年指出这是一个Windows CLR的限制:
This is a well known limitation in the Windows version of the CLR. It uses Windows' built-in support for exception handling (SEH). Problem is, it is stack frame based and a method has only one stack frame. You can easily solve the problem by moving the inner try/catch block into another helper method, thus creating another stack frame. Another consequence of this limitation is that the JIT compiler won't inline any method that contains a try statement.

Edit: 在复习CLR Via C#一书时,书中也提到了Windows系统的这一限制。更专业的说法是Windows重置了异常堆栈的起点, 直接throw不影响CLR对异常起点的认知。

那么Linux下呢, 我在WSL下看了看是确实没有这个问题.

➜  CanaryNetCore git:(master) ✗ dotnet run

Unhandled Exception: System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.Exception: Hold on..
   at PluginTestCore.Processor.Runner(DateTime dateTime)
   --- End of inner exception stack trace ---
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)
   at System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments)
   at CanaryNetCore.PluginRunner.ExecuteCore() in /mnt/c/DevLab/Canary/CanaryNetCore/PluginRunner.cs:line 33
   at CanaryNetCore.Program.Main(String[] args) in /mnt/c/DevLab/Canary/CanaryNetCore/Program.cs:line 18

➜  CanaryNetCore git:(master) ✗ cat cat PluginRunner.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.ExceptionServices;
using System.Text;
using System.Threading.Tasks;

namespace CanaryNetCore
    class PluginRunner
        public void ExecuteCore()
            Assembly assembly = Assembly.LoadFrom("PluginTestCore.dll");

            object obj = assembly.CreateInstance("PluginTestCore.Processor");
            MethodInfo m = obj.GetType().GetMethod("Runner");

            if (m != null)
                object[] methodParams = new object[] { DateTime.Now };
                    var result = m.Invoke(obj, methodParams);
                catch (Exception ex)
                    //if (ex.InnerException != null)
                    //    ExceptionDispatchInfo.Capture(ex.InnerException).Throw();


第二种方法是直接throw,在外面处理InnerException, 由于我不想修改外部代码, 所以使用了第一种方法.
The recommended way to re-throw an exception is to simply use the throw statement in C# and the Throw statement in Visual Basic without including an expression. This ensures that all call stack information is preserved when the exception is propagated to the caller.


595mm in daily life


最近搬家,需要买一些家电.由于厨房只有一道约60公分的空间,在买家电的时候关注了一下尺寸. 惊奇的发现很多家电都有一个相似的尺寸:595mm.

  • 单开门冰箱
  • 滚筒洗衣机,干衣机
  • 洗碗机

我想这肯定是装修时有意如此设计的,留下一道60公分的空间来放置家电.也就是说多年前这个宽度已经成为行业标准,且各个家电行业的设计都在遵循这个标准. 由此想到计算机的硬件接口设计也是如此, 外部接口USB/SATA/VGA&HDMI多年只在进化且保持向下兼容.

不过计算机内部的接口就没有那么’友好’了,CPU/内存针脚隔代基本不兼容. 反之推想,各种电器内部也肯定经过了多重改进/改良.


Git bash alias config

SourceTree是一个功能很强大的Git GUI工具,但是就是太慢,且在Windows下会出现莫名占用一个核心CPU。于是开始折腾Git bash,目前基本够用。其中alias部分的配置文件如下,供参考,放到 /username/.gitconfig里面即可。

l = log --pretty=format:"%C(yellow)%h\ %ad%Cred%d\ %Creset%s%Cblue\ [%cn]" --decorate --date=short
a = add
ap = add -p
c = commit --verbose
ca = commit -a --verbose
cm = commit -m
cam = commit -a -m
m = commit --amend --verbose
d = diff
ds = diff --stat
dc = diff --cached
s = status
ss = status -s
co = checkout
cob = checkout -b
# list branches sorted by last modified
b = "!git for-each-ref --sort='-authordate' --format='%(authordate)%09%(objectname:short)%09%(refname)' refs/heads | sed -e 's-refs/heads/--'"
lg1 = log --graph --abbrev-commit --decorate --format=format:'%C(bold blue)%h%C(reset) - %C(bold green)(%ar)%C(reset) %C(white)%s%C(reset) %C(dim white)- %an%C(reset)%C(bold yellow)%d%C(reset)' --all
lg2 = log --graph --abbrev-commit --decorate --format=format:'%C(bold blue)%h%C(reset) - %C(bold cyan)%aD%C(reset) %C(bold green)(%ar)%C(reset)%C(bold yellow)%d%C(reset)%n'' %C(white)%s%C(reset) %C(dim white)- %an%C(reset)' --all
lg = !"git lg1"
# list aliases
la = "!git config -l | grep alias | cut -c 7-"

其中我最喜欢的是git lg,附图2张,右键查看大图(一个“MD程序员”上镜)。
git lg
git lg2


-='cd -'
1='cd -'
2='cd -2'
3='cd -3'
4='cd -4'
5='cd -5'
6='cd -6'
7='cd -7'
8='cd -8'
9='cd -9'
afind='ack -il'
d='dirs -v | head -10'
ga='git add'
gaa='git add --all'
gap='git apply'
gapa='git add --patch'
gau='git add --update'
gb='git branch'
gba='git branch -a'
gbd='git branch -d'
gbda='git branch --no-color --merged | command grep -vE "^(*|\s*(master|develop|dev)\s*$)" | command xargs -n 1 git branch -d'
gbl='git blame -b -w'
gbnm='git branch --no-merged'
gbr='git branch --remote'
gbs='git bisect'
gbsb='git bisect bad'
gbsg='git bisect good'
gbsr='git bisect reset'
gbss='git bisect start'
gc='git commit -v'
'gc!'='git commit -v --amend'
gca='git commit -v -a'
'gca!'='git commit -v -a --amend'
gcam='git commit -a -m'
'gcan!'='git commit -v -a --no-edit --amend'
'gcans!'='git commit -v -a -s --no-edit --amend'
gcb='git checkout -b'
gcd='git checkout develop'
gcf='git config --list'
gcl='git clone --recursive'
gclean='git clean -fd'
gcm='git checkout master'
gcmsg='git commit -m'
'gcn!'='git commit -v --no-edit --amend'
gco='git checkout'
gcount='git shortlog -sn'
gcp='git cherry-pick'
gcpa='git cherry-pick --abort'
gcpc='git cherry-pick --continue'
gcs='git commit -S'
gcsm='git commit -s -m'
gd='git diff'
gdca='git diff --cached'
gdct='git describe --tags git rev-list --tags --max-count=1'
gdcw='git diff --cached --word-diff'
gdt='git diff-tree --no-commit-id --name-only -r'
gdw='git diff --word-diff'
gf='git fetch'
gfa='git fetch --all --prune'
gfo='git fetch origin'
gg='git gui citool'
gga='git gui citool --amend'
ggpull='git pull origin $(git_current_branch)'
ggpush='git push origin $(git_current_branch)'
ggsup='git branch --set-upstream-to=origin/$(git_current_branch)'
ghh='git help'
gignore='git update-index --assume-unchanged'
gignored='git ls-files -v | grep "^[[:lower:]]"'
git-svn-dcommit-push='git svn dcommit && git push github master:svntrunk'
gk='\gitk --all --branches'
gke='\gitk --all $(git log -g --pretty=%h)'
gl='git pull'
glg='git log --stat'
glgg='git log --graph'
glgga='git log --graph --decorate --all'
glgm='git log --graph --max-count=10'
glgp='git log --stat -p'
glo='git log --oneline --decorate'
glog='git log --oneline --decorate --graph'
gloga='git log --oneline --decorate --graph --all'
glol='git log --graph --pretty='\''%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset'\'' --abbrev-commit'
glola='git log --graph --pretty='\''%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset'\'' --abbrev-commit --all'
glum='git pull upstream master'
gm='git merge'
gma='git merge --abort'
gmom='git merge origin/master'
gmt='git mergetool --no-prompt'
gmtvim='git mergetool --no-prompt --tool=vimdiff'
gmum='git merge upstream/master'
gp='git push'
gpd='git push --dry-run'
gpoat='git push origin --all && git push origin --tags'
gpristine='git reset --hard && git clean -dfx'
gpsup='git push --set-upstream origin $(git_current_branch)'
gpu='git push upstream'
gpv='git push -v'
gr='git remote'
gra='git remote add'
grb='git rebase'
grba='git rebase --abort'
grbc='git rebase --continue'
grbi='git rebase -i'
grbm='git rebase master'
grbs='git rebase --skip'
grep='grep --color=auto --exclude-dir={.bzr,CVS,.git,.hg,.svn}'
grh='git reset HEAD'
grhh='git reset HEAD --hard'
grmv='git remote rename'
grrm='git remote remove'
grset='git remote set-url'
grt='cd $(git rev-parse --show-toplevel || echo ".")'
gru='git reset --'
grup='git remote update'
grv='git remote -v'
gsb='git status -sb'
gsd='git svn dcommit'
gsi='git submodule init'
gsps='git show --pretty=short --show-signature'
gsr='git svn rebase'
gss='git status -s'
gst='git status'
gsta='git stash save'
gstaa='git stash apply'
gstc='git stash clear'
gstd='git stash drop'
gstl='git stash list'
gstp='git stash pop'
gsts='git stash show --text'
gsu='git submodule update'
gts='git tag -s'
gtv='git tag | sort -V'
gunignore='git update-index --no-assume-unchanged'
gunwip='git log -n 1 | grep -q -c "--wip--" && git reset HEAD~1'
gup='git pull --rebase'
gupv='git pull --rebase -v'
gwch='git whatchanged -p --abbrev-commit --pretty=medium'
gwip='git add -A; git rm $(git ls-files --deleted) 2> /dev/null; git commit --no-verify -m "--wip-- [skip ci]"'
history='fc -l 1'
l='ls -lah'
la='ls -lAh'
ll='ls -lh'
ls='ls --color=tty'
lsa='ls -lah'
md='mkdir -p'