0

I have a situation where I have a very large list of objects for which I want to call a user-defined list of methods (it's for building a generic report of the list of objects). The method signature is provided as MethodInfo (via reflection). Right now, I use method.Invoke to call this method for all of my individual objects to retrieve the individual values of my objects.

Something along those lines:

private void writeReport (List<object> rows, MethodInfo[] columns, string dest){
   System.IO.StreamWriter writer = new System.IO.StreamWriter(dest);

   StringBuilder builder = new StringBuilder();
   foreach(object row in rows){
      foreach(MethodInfo m in columns){
         builder.Append(m.Invoke(row).ToString());
      }
      writer.WriteLine(builder.ToString());
      builder.Clear();
   }

   writer.Close();
}

Note: This is just a dummy method to get an idea of my framework. There are obviously things missing like a header or separator signs etc.

I read about DelegateReflection and how much faster it is to the regular reflection Invoke call but the examples I found never have to call the same method on different objects. It's always a static method rather then a member method of an object.

Is there any other way how I can make this faster? Note that my list of "rows" can easily contain > 10'000'000 elements.

ALSO NOTE: I have seen this post MethodInfo.Invoke performance issue

However, I was wondering whether something better has come up since (also empirically trying this suggestion did not yield any performance gain).

Oliver Bernhardt
  • 399
  • 1
  • 4
  • 18
  • What are the actual types contained within `rows`? Can you define an `interface` for those object types to implement instead? – Dai Dec 10 '19 at 08:00
  • Also, your code seems dangerous - what happens if someone pipes-in an object with a method named `String FireZeMissiles()` which returns a `String` report of [megadeaths](https://en.wikipedia.org/wiki/Megadeath) (and not simply `"I am le tired"`)? – Dai Dec 10 '19 at 08:01
  • Can you make some example to show what is writeReport paramter input? – TimChang Dec 10 '19 at 08:08
  • Yes, we absolutely can look at delegates for this; however, it would be much simpler and more efficient to *not use `MethodInfo` in the first place* - there are much better APIs, such as just a `Func` (although we can do better than that) - is it possible to change the API at all? – Marc Gravell Dec 10 '19 at 08:08
  • also: it is kinda awkward dealing in `object`; a generic `WriteReport` that took a `List` i.e. elements of the correct input type `T` would be great – Marc Gravell Dec 10 '19 at 08:09
  • finally: what are the signatures of the `MethodInfo` here? presumably they are instance methods on a particular `T`, but: do they return `object`? can they return something else, i.e. `string` etc? and they are always parameterless? – Marc Gravell Dec 10 '19 at 08:10
  • Consider this to be a framework that can export fields of any list of objects by crawling through methods that follow a certain signature (no parameter and any output one can call "ToString" on). The user is first presented with a list of all the methods that follow this signature (where he can select which ones he is interested) and then calls the export method on this list of objects. – Oliver Bernhardt Dec 10 '19 at 11:50

1 Answers1

1

Frankly, personally I'd change the API to use generics and delegates in the API:

    private void WriteReport<T>(List<T> rows, Func<T, string>[] columns, string dest)
    {
        using (var writer = new System.IO.StreamWriter(dest))
        {
            foreach (T row in rows)
            {
                foreach (var m in columns)
                {
                    writer.Write(m(row));
                }
                writer.WriteLine();
                writer.Flush();
            }
            writer.Close();
        }
    }

Then you can just use lambdas at the call site, i.e.

List<Foo> foos = ...
yourObject.writeReport(foos, new[] { x => x.SomeMethod(), x => x.AnotherMethod() }, path);

However, if that isn't possible, then: something like the following should work nicely:

    static readonly ParameterExpression s_sharedParam = Expression.Parameter(
        typeof(object), "obj");
    static readonly ParameterExpression[] s_sharedParams = new[] { s_sharedParam };
    static Func<object,object> AsFuncObjectObject(MethodInfo method)
    {
        Expression body = Expression.Call(
            Expression.Convert(s_sharedParam, method.DeclaringType), method);
        if (method.ReturnType.IsValueType) // manually box
            body = Expression.Convert(body, typeof(object));
        return Expression.Lambda<Func<object, object>>(body, s_sharedParams).Compile();
    }

    private void writeReport(List<object> rows, MethodInfo[] columns, string dest)
    {
        using (var writer = new System.IO.StreamWriter(dest))
        {
            var delegates = Array.ConvertAll(columns, col => AsFuncObjectObject(col));
            foreach (object row in rows)
            {
                foreach (var m in delegates)
                {
                    writer.Write(m(row).ToString());
                }
                writer.WriteLine();
                writer.Flush();
            }
            writer.Close();
        }
    }

Note: the use of Expression here is to deal with the ambiguity of the method signature, and the lack of generics. If the input type is well known as a T, and the return type is known (presumably either string or object), then you can use Delegate.CreateDelegate directly, simply specifying null as the target object. Delegate.CreateDelegate allows a special usage where if you pass in a null target as the target instance of an instance method, and you specify a delegate type with one-too-many parameters, then it treats the first parameter as the per-call target instance. So; if you are dealing with generic T and string, you could use simply:

private void WriteReport<T>(List<T> rows, MethodInfo[] columns, string dest)
{
    // ...
    var delegates = Array.ConvertAll(columns,
        col => (Func<T, string>)Delegate.CreateDelegate(typeof(Func<T, string>), col));
    foreach (T row in rows)
    {
        foreach (var m in delegates)
        {
            writer.Write(m(row));
    // ...
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900