我怎么能在C#中的IEnumerables上实现JQuery样式方法调用?

时间:2014-09-19 15:38:21

标签: c# generics dynamic

在JQuery中,您可以编写$('.my-class').hide(),并在所有结果上调用hide()。没有for循环,没有迭代,没有LINQ扩展和lambdas等等,它使得处理列表非常有趣。我希望能够在C#中的IEnumerables上使用此功能。我认为Matlab在对数组/矩阵进行操作时具有类似的简洁语法。

长话短说,我希望以下代码(或类似代码)能够正常工作:

class Program
{
    static List<MyClass> MyList = new List<MyClass>();
    static void Main(string[] args)
    {
        for (int i = 0; i < 100; i++)
            MyList.Add(new MyClass());

        MyList.MyMethod();
        // should be exactly equivalent to:
        MyList.Select(n => n.MyMethod());
    }
}

class MyClass
{
    public int MyMethod() { return 123; }
}

我知道这可以在个案基础上使用扩展方法:

public static IEnumerable<int> MyMethod(this IEnumerable<MyClass> lst)
{
    return lst.Select(n => n.MyMethod());
}

但我们必须为您希望此行为的每种类型上的每个方法创建一个扩展方法。

理想情况下,这对所有类型和所有方法都是可能的,并且在编译时仍然是类型安全的。我怀疑我在这里要求C#语言过多,但是我们将如何以尽可能通用的方式做到这一点或类似事情?

可能的解决方案:

  1. 自动生成特定类型的扩展方法。如果我们只打算对几种类型使用这种表示法,我们可以自动生成一次扩展方法。这将实现确切的语法和完整的类型安全,但生成代码将是一个痛苦。

  2. 单个扩展方法,它返回使用提供的类型的反射构建的动态对象。我们的想法是我们使用反射来迭代类型的方法并构建动态具有.MyMethod()之类的所有方法的对象将在IEnumerable上调用Select(...)。语法最终会像MyList.Selector().MyMethod()一样。但现在我们已经失去了语法和类型安全性。聪明,也许吧。有用,可能不是。

  3. 拦截方法调用?是否可以决定如何在运行时对方法调用做出反应?我不知道。你再次失去了类型安全性。

2 个答案:

答案 0 :(得分:1)

最简单的解决方案是使用动态对象。如果你愿意抛弃类型安全性,你可以创建一个IEnumerable类型,在需要时动态静态,动态,否则,这里是一个示例原型:

public class DynamicIEnumerable<T> : DynamicObject, IEnumerable<T>
{
     public IEnumerable<T> _enumerable;
     public DynamicIEnumerable(IEnumerable<T> enumerable)
     {
         this._enumerable = enumerable;
     }

     public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
     {
         result = new DynamicIEnumerable<T>(_enumerable.Select(x => (T)typeof(T).InvokeMember(binder.Name, BindingFlags.InvokeMethod, null, x, null)));
         return true;
     }

     public IEnumerator<T> GetEnumerator()
     {
         return _enumerable.GetEnumerator();
     }

     IEnumerator IEnumerable.GetEnumerator()
     {
         return _enumerable.GetEnumerator();
     }
  }

TryInvokeMember中,IENumerable上的调用成员将使用反射应用于所有项目。这种方法的唯一限制是您必须在调用的方法中返回this。以下是如何使用此方法的示例:

 public class SomeClass
 { 
    public int Value {get;set;}
    public SomeClass(int value)
    {
        this.Value = x;
    }
    public SomeClass Plus10()
    {
        Value += 10;
        return this;
    }
 }


 static void Main()
 {
     dynamic d = new DynamicIEnumerable<X>(Enumerable.Range(0, 10).Select(x => new SomeClass(x)));
     foreach (var res in d.Plus10().Plus10())
         Console.WriteLine(res.Value);
 }

答案 1 :(得分:-1)

  

我们如何以尽可能通用的方式做到这一点或类似事情?

这不是一个很好的解决方案,但确实有效:

public class MyClass
{
  public void MyMethod()
  {
  }
  public void MyMethod2()
  {
  }
}

扩展方法:

public static class WeirdExtensions
{
    public static IEnumerable<T> CallOnAll<T>(this IEnumerable<T> instance , 
                                                Action<T> call)
    {
        foreach(var item in instance)
        {
            call(item);
        }

        return instance;
    }
}

用法(链接/流利):

var blah = new List<MyClass>();

blah.CallOnAll(b => b.MyMethod())
  .CallOnAll(b => b.MyMethod2());

备注

由于您必须every single method on every single type的基本假设,这不太可能。在jQuery / Html中,只有一种基础类型的Html元素。所有元素都暴露于相同的方法(无论该类型是否支持它)。在jQuery中,你可以调用$('head').hide()但它不会在视觉上做任何事情,但因为它是一个元素,它将是内联样式。如果您需要一个新方法,那么您确实有一个构建方法,但仅适用于一个类型,因为只有一个类型。

与C#相比,构建您的类型(许多类型)并且它们都有不同的方法(确保可能存在重叠)。