3

Edit: I am using DataSourceProviderService.InvokeAddNewDataSource method to display Data Source Configuration Wizard during design-time in Visual Studio. If user choose an object (as explained here), and click finish, I will get a string like "Namespace.ClassName". To display the Properties of the selected object in designer, I need to find the correct Type of the object in an optimized manner.

I have the name of a class and its namespace (Application.Data.Employee). I want to find the type of the class (Employee) with this information. At present I am using the following code to find the type

string classNameWithNameSpace = "Application.Data.Employee";
Type target;                                
foreach(Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
    foreach (Type t in assembly.GetTypes())
    {
        if (t.FullName.Equals(classNameWithNameSpace))
        {
            target = t;
            break;
        }
    }

Note: Assembly might be present in any dll referenced in the project. My code also supports .Net Framework 2.0

I know this is not the best way because of the following reasons

1) Two or more assemblies might have same namespace name and class name

2) I saw a SO post stating, it will throw NotSupportedException for dynamic assemblies

3) On debugging found that Types in unwanted or unnecessary assemblies are checked in the loop. AppDomain.CurrentDomain.GetAssemblies() method returns 146 assemblies in a simple project during design-time debugging

4) If above code loads an unnecessary assembly into memory, it will present in memory till application domain is present (check unloading an assembly section in this link https://msdn.microsoft.com/en-us/library/mt632258.aspx)

Is there any recommended way or best approach for doing the same?

Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
Kira
  • 1,403
  • 1
  • 17
  • 46
  • 1
    `Type.GetType(...)`? – Dmitry Bychenko Jun 14 '16 at 08:55
  • [this](http://stackoverflow.com/questions/79693/getting-all-types-in-a-namespace-via-reflection) might help, and maybe [this](http://stackoverflow.com/questions/343869/taking-out-all-classes-of-a-specific-namespace) also – Mong Zhu Jun 14 '16 at 08:56
  • @MongZhu, my code also does the same but these are not best or good approaches to solve this – Kira Jun 14 '16 at 09:02
  • On a side note, if two classes with exactly same fully qualified type name exist in two separate assemblies, how would they be of any use in real programming scenario. Compiler will never be able to distinguish them if you refer both assemblies in your program – RBT Jun 14 '16 at 09:05
  • @RBT, In windows forms, data source wizard available in designer for data binding. Using this we can choose any type as data source during design time. I am using this for my control and the wizard returns a string with namespace and class name. I need to identify the correct type from the string during design time – Kira Jun 14 '16 at 09:15
  • @Anand If you have access to an `IServiceProvider` like when you are creating a `TypeConverter` or `UITypeEditor` there is more elegant solution for the problem to find `Type` at design-time. Where your code is running? – Reza Aghaei Jul 03 '16 at 23:50
  • @RezaAghaei, My code runs after a user choose an object as data source from the **Data Source Configuration Wizard** https://msdn.microsoft.com/en-us/library/ms171892.aspx#Anchor_3. – Kira Jul 04 '16 at 08:53
  • So you are working at Design-time (which is run-time of visual studio). Can you share a bit more? Where do you inject the logic or how you are customizing the behavior? Where those types will be extracted an be used? – Reza Aghaei Jul 04 '16 at 14:01
  • *My code runs after a user choose an object as data source from the Data Source Configuration Wizard* I'm interested to know how you are performing such customization if you mind to share :) Can you share a bit more? Where do you inject the logic or how you are customizing the behavior? Where those types will be extracted an be used? – Reza Aghaei Jul 05 '16 at 19:58
  • I've got all available types or filtered them based on a base class or interface in many different places in visual studio, in t4 templates, in type converters, in ui type editors, in visual studio add-on. It's the same way that visual studio use. Using `ITypeResolutionService` needs an `IServiceProvider`. – Reza Aghaei Jul 05 '16 at 20:03
  • @RezaAghaei, yes, there are about 146 assemblies during design time. I think most of them are used by Visual Studio. I need an optimized solution because atleast 50 assemblies are searched unnecessarily to find a class chosen by user – Kira Jul 06 '16 at 12:25
  • @RezaAghaei, https://msdn.microsoft.com/en-us/library/system.componentmodel.design.data.datasourceproviderservice.invokeaddnewdatasource(v=vs.100).aspx, I am not sure adding above method in question will hep solving the problem – Kira Jul 06 '16 at 12:39
  • I can share the code which I use in a `TypeDescriptor` to list types in project. Would be it helpful? Adding the link to question and sharing a bit more context about it may be helpful. – Reza Aghaei Jul 06 '16 at 12:42
  • I have edited my question, hope it helps. Your code might be helpful because I want to find a Type in project quicker and reliable than the code posted in question – Kira Jul 06 '16 at 13:13
  • @Anand I shared an answer. If you share an implementation, maybe I can enhance the answer or put some more information in comments. Currently I have no idea about how did you call `InvokeAddNewDataSource ` or where you implemented `DataSourceProviderService` yourself or getting it from designer. Anyway hope you find it helpful :) – Reza Aghaei Jul 06 '16 at 14:26
  • var dataSource = ((DataSourceProviderService)MyControl.Site.GetService(typeof(DataSourceProviderService))).InvokeAddNewDataSource(this, FormStartPosition.Center); – Kira Jul 06 '16 at 15:13
  • @Anand I had not seen your last comment. I saw it just now. I posted the exact code which you need to get properties. It's a real design-time solution :) – Reza Aghaei Jul 06 '16 at 22:30

3 Answers3

3

You can use these services to work with types at design-time:

For example you can write such methods and pass them a suitable IServiceProvider which can get those service:

Type GetTypeByName(IServiceProvider provider, string typeName)
{
    var svc= (ITypeResolutionService)provider.GetService(typeof(ITypeResolutionService));
    return svc.GetType(typeName);
}
private List<Type> GetAllTypes(IServiceProvider provider)
{
    var svc= (ITypeDiscoveryService)provider.GetService(typeof(ITypeDiscoveryService));
    return svc.GetTypes(typeof(object), true).Cast<Type>().ToList();
}

I've used these mechanism in TypeConverter, UiTypeEditor, T4 templates and Add-ons. It's the same way that visual studio uses to work with types at design-time.


Here is the exact code you need to get properties:

var svc = ((DataSourceProviderService)Site.GetService(typeof(DataSourceProviderService)));
if (svc != null)
{
    var result = svc.InvokeAddNewDataSource(this, FormStartPosition.CenterScreen);
    if(result!=null && result.DataSources.Count>0)
    {
        var type = GetTypeByName(this.Site, result.DataSources[0].TypeName);
        var properties = type.GetProperties().ToList();
        MessageBox.Show(string.Join(",", properties.Select(x => x.Name)));
    }
}
Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
  • Thanks, I'll give it a try – Kira Jul 06 '16 at 15:02
  • @Kira What was the result, could you confirm the solution is working? I could use the code in designer in visual studio and it worked properly. – Reza Aghaei Jul 30 '16 at 00:38
  • Please be patient, it looks like you have edited your code. But I did not receive notification about this. I will check and let you know soon – Kira Aug 01 '16 at 04:29
1

The inner loop is equivalent to calling assembly.GetType(classNameWithNameSpace), so you can skip it completely. This should take care of item 3 from your list.

Item 2 can be solved by ensuring that Assembly does not have IsDynamic flag in .NET 4.0, or checking the namespace prior to 4.0.

This code is suitable for .NET 2.0

IList<Type> matchingTypes = new List<Type>();
foreach(Assembly a in AppDomain.CurrentDomain.GetAssemblies()) {
    // Skip dynamic assemblies.
    if (a.GetType().StartsWith("System.Reflection.Emit.")) {
        continue;
    }
    Type t = a.GetType(classNameWithNameSpace);
    if (t != null) {
        matchingTypes.Add(t);
    }
}

Rewrite with LINQ and IsDynamic after .NET 4.0:

var matchingTypes = AppDomain
    .CurrentDomain
    .GetAssemblies()
    .Where(a => !a.IsDynamic)
    .Select(a => a.GetType(classNameWithNameSpace))
    .Where(t => t != null)
    .ToList();

The above gives you a list of all types with classNameWithNameSpace.

Dealing with item #1 is something best left to your application. You need to decide what to do with each of the types on the matchingTypes list.

It is useful to remember about type forwarding. The list above will include both types. You can use TypeForwardedToAttribute to decide which type you should actually take.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • LINQ is supported from 3.5 framework and IsDynamic is supported from framework 4. I need to support 2.0 framework too. So I used for loop in my code – Kira Jun 14 '16 at 09:20
  • 1
    @Anand You may want to mention this somewhere in your post then: I had no idea that you have to support .NET 2.0. – Sergey Kalinichenko Jun 14 '16 at 09:21
  • item 3 is to avoid searching unnecessary assemblies not the inner for loop. There are more than 100 assemblies during design-time – Kira Jul 06 '16 at 13:20
  • if it is possible to check whether a namespace is present in an assembly, we can avoid unnecessary searching. Because number of namespaces is very less compared to Type – Kira Jul 06 '16 at 13:29
1

As you said that the search in your algorithm is also scanning unwanted assemblies. In case you plan to search only your own product's assemblies then you can leverage the standard nomenclature of the assemblies in case you have it. This will dramatically reduce the targeted assemblies which are scanned for the target type. Line # XYZ does the initial task of filtering the relevant assemblies assuming all the assemblies to be searched have a some standard prefix MyCompanyName.MyProductName in their name. Also I've replaced most of your calls with LINQ calls which are syntactically lot more cleaner.

string classNameWithNameSpace = "Application.Data.Employee";
            Type target;
            var assemblyList = AppDomain.CurrentDomain.GetAssemblies();
            //line # XYZ
            var filteredAssembliesOfMyProduct =
                assemblyList.Where(x => x.FullName.StartsWith("MyCompanyName.MyProductName"));

            foreach (Assembly assembly in filteredAssembliesOfMyProduct)
                if (assembly.GetTypes().Any(x => x.FullName == classNameWithNameSpace))
                {
                    target = assembly.GetTypes().First(x => x.FullName == classNameWithNameSpace);
                    break;
                }
RBT
  • 24,161
  • 21
  • 159
  • 240
  • the problem is data source wizard shows class name from all possible assemblies. Data source wizard belongs to VS so I should search system assemblies too. I can see my code taking long time if users of my control add too many assemblies – Kira Jun 14 '16 at 09:52
  • AppDomain.CurrentDomain.GetAssemblies() returns an array of 146 assemblies in a simple project during debugging. – Kira Jun 14 '16 at 09:58