为dynamic添加智能感知

     dynamic的出现,给了我们另一片天空。可是这片蓝天下究竟有几颗大树呢?没有人知道,因为dynamic还没有带给我们心电感应。

     dynamic大致有以下几点令人诟病的地方:

其一:效率。关于这点,其实很简单,只要你能忍受住第一次的疼,那后面的春光无限将任你遨游,当然,开销还是少不了的。
其二:与扩展方法的兼容性。dynamic有一个很大的好处,就是语义上的优雅感,而扩展方法也同样带来了语义上的优雅感。可是,鱼与熊掌不可兼得,要想在扩展方法里传递dynamic类型的参数,就只能用传统静态方法调用方式去调用了。
其三:受访问性限制的方法和调用显式实现的接口方法。这个不解释,打游击战吧。
其四:没有智能感知。这个也就是本文要解决的内容了。也许在将来微软会考虑给它加上智能感知,可是,至少不是现在。唯有自己动手,才能丰衣足食。此外Resharp5也为dynamic加入了部分的智能感知功能。
它提供的功能是:dynamic实例使用过一次的属性或方法后,之后就能感知到这个属性或方法,但是问题在于,对于同一个类型的多个实例,这个感知并不通用,而且基类中使用过的属性,在继承类中也感知不到。
 

      接下来先介绍一下我想解决的案例。dynamic本身的应用场合不一而足,所以为它提供智能感知确实很难通用。设想,你都不知道你动态添加的是何类型,又如何去让它感知呢。这里仅就特殊的案例来管中窥豹,学会了如何去加智能感知,

      就可以在其他场合下应用自如了。

      以前想要对一个对象动态的增删改属性,会如何处理呢?

      我们可能会提供如下一组增删改方法

        void Set(string prop, object data)
        T Query<T>(string prop, T defaultValue = default(T))       
        void Delete(string prop)

      上面这组方法的使用也很方便,只是语义优雅感上有所欠缺,就像访问属性全都写成调用方法的方式,方案上可以接受,可是心理上那个憋屈啊。

      那么有了dynamic之后我们是不是就可以指哪点哪了呢?答案是肯定的。

      本来的++操作需要写成 a.Set(Constants.PropName, a.Query<int>(Constants.PropName, 0) + 1);

      现在则只需写成a.PropName++;

      孰优孰劣,一望便知。

      可是原来的属性名可以用常量来定义,可以轻松的感知和归类所有的属性名,并且接受编译器的校验,可是现在的dynamic则没有了这层保护,当属性一多之后,维护的难度几何级数加大。

 

      问题摆出来了,下面就想办法解决吧。

      工欲善其事必先利其器,要为VS2010添加智能感知,首先要安装VS2010 SDK,可以在这里下载。

      要自定义智能感知,首先要实现ICompletionSourceProvider接口,注意,这里用到的接口都要添加一堆Microsoft.VisualStudio.XXX.dll的引用,这里不一一列出了,请自行查阅相关资料。

      这个接口很简单,只有一个方法

	public ICompletionSource TryCreateCompletionSource(ITextBuffer textBuffer)
        {
            return new UeqtDynamicCompletionSource(this, textBuffer);
        }

     

       要让VS知道这个接口实现的存在,需要用到MEF,如下Export进去。

[Export(typeof (ICompletionSourceProvider))]
[ContentType("CSharp")]
[Order(After = "default")]
[Name("UeqtDynamicCompletion")]
internal class UeqtDynamicCompletionSourceProvider : ICompletionSourceProvider

      这里Export的类型就是接口的类型,ContentType是需要控制的文件类型,CSharp就是cs文件,text就是所有的非二进制文件等等。Name则是自己随意定义的,用来标识这个实现的。Order则是在MEF中执行的次序,这里设置为

      在默认执行之后执行我们自定义的方法,因为这样就能获取到VS已经执行完毕的CompletionSourceSet。

      接下来就是实现我们在方法里调用的类internal class UeqtDynamicCompletionSource : ICompletionSource,这个类实现了ICompletionSource接口

      主要方法是void ICompletionSource.AugmentCompletionSession(ICompletionSession session, IList<CompletionSet> completionSets)

      这里传入的session就是当前文档的所有内容,而completionSets则是VS默认的行为执行完之后的结果集。

      因为一般的对象都是继承自object的,所以输入.之后必然会有自动完成的内容,也就是completionSets.Count必定是大于0的,而dynamic则不然,如图

      image

      dynamic默认情况下是没有任何自动完成列表的。我们就可以利用这个,来判断是不是dynamic的调用。在AugmentCompletionSession方法中可以首先判断

// 因为dynamic关键字的completionSets必然是Count为0的
if (completionSets.Count != 0) return;

      在讲接下来的内容前,先介绍一下,我想使用扩展属性的方式

    class Test
    {
        public dynamic ExtendedProperties = new ExpandoObject();
    }

     如上所示,会把dynamic的属性封装在ExtendedProperties属性中,使用时则是a.ExtendedProperties.XXX

     于是可以检测是不是对ExtendedProperties的调用,只有ExtendedProperties的调用,才显示自动完成列表

            // 寻找"."之前的字符串是什么
            SnapshotPoint currentPoint = (session.TextView.Caret.Position.BufferPosition) - 1;
            string allText = session.TextView.TextSnapshot.GetText();
            if (allText.Length - currentPoint.Position > UeqtDynamicHelpers.ExtendedProperties.Length)
            {
                // 检查是不是ExtendedProperties.
                if (
                    allText.Substring(
                        allText.Substring(0, currentPoint.Position).LastIndexOf('.') - UeqtDynamicHelpers.ExtendedProperties.Length,
                        UeqtDynamicHelpers.ExtendedProperties.Length) == UeqtDynamicHelpers.ExtendedProperties ||
                    (allText.Substring(currentPoint.Position, 1) == "." &&
                     allText.Substring(currentPoint.Position - UeqtDynamicHelpers.ExtendedProperties.Length,
                                       UeqtDynamicHelpers.ExtendedProperties.Length) == UeqtDynamicHelpers.ExtendedProperties))
                {

   当检测成功时,我们就生成自己的自动完成列表,如何去拿指定文件中定义的列表,先放一放,过会讲,这里先临时创建一些

var mCompList = new List<Completion>();        

mCompList.Add(new Completion(“hp”,".hp”,"这是hp,int类型", null, null)

      先介绍一下Completion构造函数的5个参数,第一个就是自动完成列表里要出现的内容,第二个则是选择了这个自动完成时要插入的内容,因为在IOleCommandTarget过滤了点,所以这里要多插入个点。

      第三个参数是描述,最后两个则是要显示的图标,例如属性的图标或是字段的图标,要用图标的话,可以使用系统默认的,例如

    _mSourceProvider.GlyphService.GetGlyph(StandardGlyphGroup.GlyphGroupProperty, StandardGlyphItem.GlyphItemPublic), "72"));

     首先需要在UeqtDynamicCompletionSourceProvider类里添加

     [Import]
     internal IGlyphService GlyphService { get; set; }

     这样就能使用系统的图标了。

     最后把自动完成列表加入列表集里

var set = new CompletionSet(
       "Ueqt",
       "Ueqt",
       FindTokenSpanAtPosition(session.GetTriggerPoint(_mTextBuffer), session),
       mCompList,
       null);

   completionSets.Add(set);

     一切似乎已经完成了,可是当我们运行起来后,就会发现,其他任何时候输入东西都会触发AugmentCompletionSession方法的调用,可是对于dynamic,却无论如何不会触发,因为在默认的IOleCommandTarget里被过滤掉了。

   

      那我们只能自己实现IOleCommandTarget了。首先注册一个IVsTextViewCreationListener的实现

    [Export(typeof(IVsTextViewCreationListener))]
    [Name("ueqt completion handler")]
    [ContentType("CSharp")]
    [TextViewRole(PredefinedTextViewRoles.Editable)]
    internal sealed class UeqtVsTextViewCreationListener : IVsTextViewCreationListener
    这个接口实现了一个方法

public void VsTextViewCreated(IVsTextView textViewAdapter)
{
    IWpfTextView view = AdaptersFactory.GetWpfTextView(textViewAdapter);
    if (view == null) return;

    UeqtCommandFilter filter = new UeqtCommandFilter(view, CompletionBroker);

    IOleCommandTarget next;
    textViewAdapter.AddCommandFilter(filter, out next);
    filter.Next = next;

     这个方法调用的internal sealed class UeqtCommandFilter : IOleCommandTarget,这个就是我们要新添加的行为

     这个接口核心的方法是public int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut)

     VS中所有的操作都会走进来,我们要做的就是在判断出输入了点之后,我们去触发AugmentCompletionSession方法。

        public int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut)
        {
            bool handled = false;
            int hresult = VSConstants.S_OK;

            // 1. Pre-process
            if (pguidCmdGroup == VSConstants.VSStd2K)
            {
                char typedChar = char.MinValue;
                //make sure the input is a char before getting it
                if (pguidCmdGroup == VSConstants.VSStd2K && nCmdID == (uint)VSConstants.VSStd2KCmdID.TYPECHAR)
                {
                    typedChar = (char)(ushort)Marshal.GetObjectForNativeVariant(pvaIn);
                }
                if ((VSConstants.VSStd2KCmdID)nCmdID == VSConstants.VSStd2KCmdID.AUTOCOMPLETE || (VSConstants.VSStd2KCmdID)nCmdID == VSConstants.VSStd2KCmdID.COMPLETEWORD)
                {
                    handled = StartSession();
                }
                else if (nCmdID == (uint)VSConstants.VSStd2KCmdID.RETURN
                   || nCmdID == (uint)VSConstants.VSStd2KCmdID.TAB
                   || (char.IsWhiteSpace(typedChar) || char.IsPunctuation(typedChar)) && typedChar != '.')
                {
                    if (nCmdID == (uint)VSConstants.VSStd2KCmdID.RETURN
                   || nCmdID == (uint)VSConstants.VSStd2KCmdID.TAB)
                    {
                        if (_currentSession != null && _currentSession.SelectedCompletionSet != null)
                        {
                            // 自动完成后不输入回车和Tab
                            handled = true;
                        }
                    }
                    else
                    {
                        handled = false;
                    }

                    Complete();
                }
                else if ((VSConstants.VSStd2KCmdID)nCmdID == VSConstants.VSStd2KCmdID.CANCEL)
                {
                    handled = Cancel();
                }
            }

            if (!handled)
                hresult = Next.Exec(pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut);

            if (ErrorHandler.Succeeded(hresult))
            {
                if (pguidCmdGroup == VSConstants.VSStd2K)
                {
                    switch ((VSConstants.VSStd2KCmdID)nCmdID)
                    {
                        case VSConstants.VSStd2KCmdID.TYPECHAR:
                            char ch = GetTypeChar(pvaIn);
                            if (ch == '.')
                            {
                                // 这里是关键,否则dynamic不会进ICompletionSource.AugmentCompletionSession
                                _currentSession = null;
                                StartSession();
                            }
                            //if (ch == ' ')
                            //    StartSession();
                            else if (_currentSession != null && _currentSession.SelectedCompletionSet != null)
                                Filter();
                            break;
                        case VSConstants.VSStd2KCmdID.BACKSPACE:
                            if (_currentSession != null && _currentSession.SelectedCompletionSet != null)
                            {
                                Filter();
                            }
                            break;
                    }
                }
            }

            return hresult;
        }

      直接上代码吧,不解释了,关键的地方写了注释了,就在那里会创建对象,触发AugmentCompletionSession。

      在Next.Exec(pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut); 上面做的都是判断自动完成是否结束了,结束就Commit或者Cancel。

     

      通过上面这些就可以实现我们要的效果了

image

 

 

 

      注意:这里不会自动对自动完成列表进行Filter,但是,还是会高亮选择和自动滚动之类的。要实现Filter需要实现其他类。

 

这里介绍一下VS2010的Extension Manager,我们的工程编译出来之后是UeqtDynamicIntellisense.vsix文件和一个dll,双击执行vsix文件之后就会进行extension的安装。

安装完成后在VS2010的Extension Manager中就能看到

image

 

      第一步算是完成了。接下来如果要维护代码,看到了test.ExtendedProperties.Hp这行内容,如何知道这个HP是啥呢?

      默认的显示是

image

 

      我们希望这里能显示出一些提示信息,那么我们需要实现和注册IQuickInfoSourceProvider接口,以及实现IQuickInfoSource的public void AugmentQuickInfoSession(IQuickInfoSession session, IListquickInfoContent, out ITrackingSpan applicableToSpan)方法

     这里的实现步骤和上面的内容很相似,就不赘述了,给出实现后的效果图

     image

 

      这样我们需要的效果就基本到位了。等等,最重要的是不是还没说呢?怎么去自动根据已经写好的文件来读取要添加的自动完成呢?

      例如solution里有这样一个文件UeqtExtendedPropertiesConstants.cs

namespace Ueqt
{
    class UeqtExtendedPropertiesConstants
    {
        /// <summary>
        /// 这是HP
        /// string
        /// </summary>
        public const string Hp = "hp";

    }
}

     要自动从这个文件里读取到HP的信息,并且能随改随更新

     其实也不难,只要找到了方法。

// 找到这个文件

EnvDTE.ProjectItem item = UeqtDynamicHelpers.FindProjectItem("UeqtExtendedPropertiesConstants.cs");

                  if (item != null)
                  {
                        FileCodeModel fileCM = item.FileCodeModel;
                       CodeNamespace myNameSpace = (CodeNamespace)fileCM.CodeElements.Item("Ueqt");
                        if (myNameSpace == null)
                        {
                            return;
                        }
                        CodeClass myClass = (CodeClass)myNameSpace.Children.Item("UeqtExtendedPropertiesConstants");
                        if (myClass == null)
                        {
                            return;
                        }
                        foreach (CodeElement ele in myClass.Members)
                        {
                            string name = ele.Name;
                            string docComment = string.Empty;
                            CodeVariable var = ele as CodeVariable;
                            if(var!=null)
                            {
                                string comment = var.Comment;
                                docComment = UeqtDynamicHelpers.ShowDocComment(var.DocComment);
                            }

                            mCompList.Add(new Completion(name, "." + name, docComment, _mSourceProvider.GlyphService.GetGlyph(StandardGlyphGroup.GlyphGroupProperty, StandardGlyphItem.GlyphItemPublic), "72"));
                        }

 

 

 

                 }

 

      就是这个FileCodeModel ,有了它,你就不用自己再去分析一遍文本内容了。

      这样智能感知就算基本到位了,后续可以做的工作就是为dynamic添加编译时的自动校验,检查属性是否存在,这也不难,不过要检查类型是否匹配,似乎比较复杂。

      先到这里吧。也许下一个版本的VS就会对dynamic提供智能感知了吧?但愿如此,期待吧。

转载于:https://www.cnblogs.com/ueqtxu/archive/2010/08/15/1800095.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值