2
\$\begingroup\$

If you write a VS extension, you can get access to VS services with the IServiceProvider interface:

var vsUIShell = (IVsUIShell)serviceProvider.GetService(typeof(SVsVsUIShell));
vsUIShell.SetWaitCursor(); // for example

To reduce duplication every time I need this, I created a class like this:

static class VisualStudioServices
{
    public static IVsUIShell UIShell { get { return vsUIShell.Service; } } 
    static readonly LazyService<SVsUIShell, IVsUIShell> vsUIShell = new LazyService<SVsUIShell, IVsUIShell>();

    class LazyService<TService, T>
    {
        static T GetService()
        {
            return (T)ServiceProvider.GlobalProvider.GetService(typeof(TService));
        }

        readonly Lazy<T> serviceLazy = new Lazy<T>(GetService);

        public T Service { get { return this.serviceLazy.Value; } }
    }
}

To add a new service to the list, I have to mention its name 8 time (assuming the Service type and the interface type are the same, which is not always true).

I'm looking for a way to reduce this duplication further. Any suggestions?

\$\endgroup\$
3
\$\begingroup\$

Your current design will potentially make unit testing code which uses it hard. Consider making the VisualStudioServices class non-static and create an interface for it. You could then inject the ServiceProvider as a dependency rather having an implicit dependency on the static GlobalProvider. Something like this:

interface IVisualStudioServices
{
    IVsUIShell UIShell { get; }
}

class VisualStudioServices : IVisualStudioServices
{
    public IVsUIShell UIShell { get { return _VsUiShell.Service; } }

    private readonly LazyService<SVsUIShell, IVsUIShell> _VsUiShell; 

    public VisualStudioServices(ServiceProvider serviceProvider)
    {
        _VsUiShell = new LazyService<SVsUIShell, IVsUIShell>(serviceProvider);
    }

    class LazyService<TService, T>
    {
        ServiceProvider _ServiceProvider;
        LazyService(ServiceProvider serviceProvider)
        {
            _ServiceProvider = serviceProvider;
        }

        readonly Lazy<T> serviceLazy = new Lazy<T>(() => (T)_ServiceProvider.GetService(typeof(TService)));

        public T Service { get { return serviceLazy.Value; } }
    }
}

Any code requiring access to it should have a dependency on IVisualStudioServices which can be easily mocked out.

\$\endgroup\$
  • \$\begingroup\$ Yeah, unit testing is a valid concern. At least the "before" code was no worse. And as usual, difficult-to-test equals needs-refactoring, and your suggestion is a good first step. However, at this point, I'm happy with the ease of use of making everything static. If I really do need to redirect services for testing, I'll probably do something like class VSS { public static ServiceProvider ServiceProvider = ServiceProvider.GlobalProvider }, at least until the rest of the code improves. \$\endgroup\$ – Jay Bazuzi Nov 10 '13 at 23:15
1
\$\begingroup\$

Well, one option is to use reflection to instantiate all the static fields:

    static VisualStudioServices()
    {
        var fields = typeof(VisualStudioServices).GetFields(bindingAttr: BindingFlags.Static | BindingFlags.NonPublic);
        foreach (var field in fields)
        {
            field.SetValue(null, field.FieldType.GetConstructor(Type.EmptyTypes).Invoke(new object[] { }));
        }
    }

That reduces the duplication of service name from 8 to 6.

I do feel a little uncomfortable using reflection in a static ctor.

I also think it makes the code slightly obfuscated.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.