views:

25

answers:

1

Hi,

Here's the situation: I'm attempting to get a collection of all types in my assembly that implement a specific generic interface along with the generic type parameters used. I have managed to put together a Linq query to perform this but it seems awfully redunant.

I've read up on let and joins but couldn't see how to I'd use them to reduce the verbosity of this particular query. Can anyone provide any tips on how to shorten/enhance the query please?

Here's an MSTest class that currently passes and demonstrates what I'm trying to achieve:

[TestClass]
public class Sample
{
    [TestMethod]
    public void MyTest()
    {
        var results =
            (from type in Assembly.GetExecutingAssembly().GetTypes()
            where type.GetInterfaces().Any(x =>
                    x.IsGenericType &&
                    x.GetGenericTypeDefinition() == typeof(MyInterface<,>)
                  )
            select new ResultObj(type,
                type.GetInterfaces().First(x =>
                    x.IsGenericType &&
                    x.GetGenericTypeDefinition() == typeof(MyInterface<,>)
                ).GetGenericArguments()[0],
                type.GetInterfaces().First(x =>
                    x.IsGenericType &&
                    x.GetGenericTypeDefinition() == typeof(MyInterface<,>)
                ).GetGenericArguments()[1]
            )).ToList();

        Assert.AreEqual(1, results.Count);
        Assert.AreEqual(typeof(int), results[0].ArgA);
        Assert.AreEqual(typeof(string), results[0].ArgB);
    }

    interface MyInterface<Ta, Tb>
    { }
    class MyClassA : MyInterface<int, string>
    { }

    class ResultObj
    {
        public Type Type { get; set; }
        public Type ArgA { get; set; }
        public Type ArgB { get; set; }
        public ResultObj(Type type, Type argA, Type argB)
        {
            Type = type;
            ArgA = argA;
            ArgB = argB;
        }
    }
}

Regards,

Matt

+2  A: 

Here is an example that shows how to rewrite this using the let keyword:

var results = 
    (from type in Assembly.GetExecutingAssembly().GetTypes() 
     // Try to find first such interface and assign the result to 'ifc'
     // Note: we use 'FirstOrDefault', so if it is not found, 'ifc' will be null
     let ifc = type.GetInterfaces().FirstOrDefault(x => 
                x.IsGenericType && 
                x.GetGenericTypeDefinition() == typeof(MyInterface<,>))
     // Filtering and projection can now use 'ifc' that we already have
     where ifc != null 
     // Similarly to avoid multiple calls to 'GetGenericArguments'
     let args = ifc.GetGenericArguments()
     select new ResultObj(type, args[0], args[1])).ToList(); 

The let keyword works a bit like variable declaration, but lives within LINQ queries - it allows you to create a variable that stores some result that is needed in multiple places later in the query. You mention "joins" as well, but that's mostly used for database-like joins (and I'm not sure how it would apply here).

Tomas Petricek
Tomas, you're an artist, this works perfectly, thank you.My original example doesn't support this, but I wondered if it would be possible to select a type multiple times if it implemented the interface more than once with different type parameters. Would a join be used in this circumstance?
Matt
Ah, I'm getting the hang of this Linq stuff now, I managed it using a second 'from' statement in place of the 'let ifc = ...'
Matt