2015-07-12 13 views
7

Podejrzewam, że nie jest to możliwe, ale nie widziałem definitywnego nie.Czy jest możliwe utworzenie ogólnej metody na obiekcie dynamicznym/ExpandoObject

Mój obecny (pracy) jest jak następuje realizacja ..

public static Main(param args[]) 
{ 
    dynamic Repository = GetRepository(); 

    var query = (Repository.QueryUser() as IQueryable<User>) 
       .Where(user => user.Name.ToLower().Contains("jack")); 
} 

public static dynamic GetRepository() 
{ 
    dynamic repo = new System.Dynamic.ExpandoObject();   
    repo.QueryUser = new [] { new User() { Name = "Jack Sparrow"}}.AsQueryable<User>(); 
    return repo; 
} 

Aby lepiej mock (powszechnie używany) skład interfejsu z metody rodzajowe, chciałbym zaimplementować coś tak:

public interface IRepository 
{ 
    IQueryable<T> Query<T>(); 
} 

public static Main(param args[]) 
{ 
    IRepository Repository = GetRepository(); // return a dynamic 

    var query = Repository.Query<User>() 
       .Where(user => user.Name.ToLower().Contains("jack")); 
} 


public static dynamic GetRepository() 
{ 
    dynamic repo = new System.Dynamic.ExpandoObject(); 

    // the issue is on the following line 
    repo.Query<User> = new [] { 
          new User() { 
           Name = "Jack Sparrow" 
          } 
         }.AsQueryable<User>(); 
    return repo; 
} 

Czy jest możliwe obejście tego problemu (być może używając czegoś w przestrzeni nazw System.Dynamic)?

+0

Czy byłbyś w porządku, tworząc klasę, która ma na niej metodę 'Query '? – usr

+0

Podejrzewam, że nie jest to możliwe, ponieważ szablony są kompilowane do unikalnych klas i wywołań. Może to być możliwe dzięki emitowaniu kodu IL. – Rob

+0

http: // stackoverflow.com/questions/3712732/how-to-create-a-generic-list-with-a-dynamic-object-type? rq = 1 ... hmm ... może się to okazać względne –

Odpowiedz

4

Wątpię, możesz tylko uzyskać lub ustawić "właściwości" (lub events) z ExpandoObject, a nie definiować nowych metod. Jeśli chcesz to zrobić, musisz utworzyć własny obiekt dynamiczny, do którego dodasz swoich członków.

Ale czuję, że muszę to powiedzieć, dlaczego zamiast tego użyć kompozycji? Utwórz klasę, do której dodajesz swoje metody i posiadasz właściwość dla expando.

class MyClass 
{ 
    public dynamic Expando { get; } = new ExpandoObject(); 
    public void MyMethod<T>() { } 
} 

Jeśli koniecznie chciał to zrobić, można utworzyć dynamiczny obiekt z dynamicznym meta obiektu owijki nad ExpandoObject. Następnie w opakowaniu przesuń wszystkie wiązania do swoich członków do dynamicznego obiektu, a wszystkie pozostałe do rozszerzenia. Obiekt będzie tym, co go zdefiniujesz, z funkcjonalnością expando.

np

// be sure to explicitly implement IDictionary<string, object> 
// if needed forwarding all calls to the expando 
class ExtendedExpandoObject : IDynamicMetaObjectProvider 
{ 
    DynamicMetaObject IDynamicMetaObjectProvider.GetMetaObject(Expression parameter) => new MyMetaObject(parameter, this); 

    public ExtendedExpandoObject(ExpandoObject expandoObject = null) 
    { 
     Value = expandoObject ?? new ExpandoObject(); 
    } 
    public ExpandoObject Value { get; } 

    // the new methods 
    public string GetMessage() => "GOT IT!"; 
    public string GetTypeName<T>() => typeof(T).Name; 

    // be sure to implement methods to combine results (e.g., GetDynamicMemberNames()) 
    class MyMetaObject : DynamicMetaObjectWrapper 
    { 
     public MyMetaObject(Expression parameter, ExtendedExpandoObject value) 
       : base(new DynamicMetaObject(parameter, BindingRestrictions.Empty, value)) 
     { 
      var valueParameter = Expression.Property(
       Expression.Convert(parameter, typeof(ExtendedExpandoObject)), 
       "Value" 
      ); 
      IDynamicMetaObjectProvider provider = value.Value; 
      ValueMetaObject = provider.GetMetaObject(valueParameter); 
     } 
     protected DynamicMetaObject ValueMetaObject { get; } 

     public override DynamicMetaObject BindGetMember(GetMemberBinder binder) 
     { 
      if (IsMember(binder.Name)) 
       return base.BindGetMember(binder); 
      return ValueMetaObject.BindGetMember(binder); 
     } 

     public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value) 
     { 
      if (IsMember(binder.Name)) 
       return base.BindSetMember(binder, value); 
      return ValueMetaObject.BindSetMember(binder, value); 
     } 

     private bool IsMember(string name) => typeof(ExtendedExpandoObject).GetMember(name).Any(); 
    } 
} 

Dzięki temu można go używać tak jak w przypadku każdego obiektu EXPANDO.

dynamic expando = new ExtendedExpandoObject(); 
Console.WriteLine(expando.Value);   // the original expando 
expando.Length = 12;      // set a new property on the expando 
Console.WriteLine(expando.Length);   // get a property on the expando 
Console.WriteLine(expando.GetMessage()); // call the new method 
Console.WriteLine(expando.GetTypeName<ExtendedExpandoObject>()); // call the generic method 
Console.WriteLine(expando.Value.Length); // get the property on the original expando 

Będziesz po prostu potrzebują DynamicMetaObjectWrapper:

public abstract class DynamicMetaObjectWrapper : DynamicMetaObject 
{ 
    protected DynamicMetaObjectWrapper(DynamicMetaObject metaObject) 
      : base(metaObject.Expression, metaObject.Restrictions, metaObject.Value) 
    { 
     BaseMetaObject = metaObject; 
    } 
    public DynamicMetaObject BaseMetaObject { get; } 

    public override DynamicMetaObject BindBinaryOperation(BinaryOperationBinder binder, DynamicMetaObject arg) => BaseMetaObject.BindBinaryOperation(binder, arg); 
    public override DynamicMetaObject BindConvert(ConvertBinder binder) => BaseMetaObject.BindConvert(binder); 
    public override DynamicMetaObject BindCreateInstance(CreateInstanceBinder binder, DynamicMetaObject[] args) => BaseMetaObject.BindCreateInstance(binder, args); 
    public override DynamicMetaObject BindDeleteIndex(DeleteIndexBinder binder, DynamicMetaObject[] indexes) => BaseMetaObject.BindDeleteIndex(binder, indexes); 
    public override DynamicMetaObject BindDeleteMember(DeleteMemberBinder binder) => BaseMetaObject.BindDeleteMember(binder); 
    public override DynamicMetaObject BindGetIndex(GetIndexBinder binder, DynamicMetaObject[] indexes) => BaseMetaObject.BindGetIndex(binder, indexes); 
    public override DynamicMetaObject BindGetMember(GetMemberBinder binder) => BaseMetaObject.BindGetMember(binder); 
    public override DynamicMetaObject BindInvoke(InvokeBinder binder, DynamicMetaObject[] args) => BaseMetaObject.BindInvoke(binder, args); 
    public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args) => BaseMetaObject.BindInvokeMember(binder, args); 
    public override DynamicMetaObject BindSetIndex(SetIndexBinder binder, DynamicMetaObject[] indexes, DynamicMetaObject value) => BaseMetaObject.BindSetIndex(binder, indexes, value); 
    public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value) => BaseMetaObject.BindSetMember(binder, value); 
    public override DynamicMetaObject BindUnaryOperation(UnaryOperationBinder binder) => BaseMetaObject.BindUnaryOperation(binder); 
    public override IEnumerable<string> GetDynamicMemberNames() => BaseMetaObject.GetDynamicMemberNames(); 
} 
+0

"Wątpię, możesz uzyskać lub ustawić" właściwości "(lub zdarzenia) obiektu ExpandoObject, a nie definiować nowych metod.". To nie jest prawda. Możliwe jest dodawanie nowych metod do obiektu ExpandoObject. Sprawdź dokumentację: https://msdn.microsoft.com/en-us/library/system.dynamic.expandoobject%28v=vs.110%29.aspx Oto przykład z dokumentacji: 'dynamic expando = new ExpandoObject(); ' ' expando.number = 10; ' ' expando.Increment = (Action) (() => {expando.number ++;}); ' ' Console.WriteLine (expando.number); // Drukuje 10' 'expando.Increment();' 'Console.WriteLine (expando.number); // Drukuje 11' –

+0

To nie jest metoda, to jest właściwość, która trzyma delegata. To nie to samo. Nie może brać udziału w przeciążaniu metody, między innymi. –

+0

Tak, masz rację, że ExpandoObject nie jest typem deklaracji metody, tylko zawiera odwołanie do metody w właściwości. Jest to również udokumentowane w podanym przeze mnie linku. Mimo to docs nadal wyjaśnia to jako metodę dodaną do ExpandoObject. Delegat jest referencją do metody (w tym przypadku lambda). To, czy jest przechowywany w właściwości dynamicznej, nie degraduje jej jako metody. –

3

Twój hipotetyczny realizacja obiektu EXPANDO nie "zaspokoić" interfejs; nie ma ogólnej metody Query<T>, którą można nazwać jako Query<Foo>() lub Query<Bar>(). Ma tylko nietypową metodę, która zwróci IQueryable<User>. To powiedziawszy, nie wydaje się, aby w każdym razie rozsądne było wstawienie interfejsu, zwłaszcza bez ograniczeń typu.

Sugerowałbym użyciu zarówno przy użyciu metod innych niż rodzajowe jak QueryUsers, QueryFoos itd., Lub posiadające interfejs IRepository<T>, który określa metodę IQueryable<T> Query(). Oto w jaki sposób można korzystać z biblioteki Impromptu-Interface wdrożyć drugie podejście:

using ImpromptuInterface; 
using ImpromptuInterface.Dynamic; 
using System.Linq; 

public interface IRepository<T> 
{ 
    IQueryable<T> Query(); 
} 

public class User 
{ 
    public string Name { get; set; } 
} 

public class Program 
{ 
    public static void Main(params string[] args) 
    { 
     IRepository<User> repo = GetUserRepository(); // dynamically construct user repository 

     var query = repo.Query() 
        .Where(user => user.Name.ToLower().Contains("jack")); 
    } 

    public static IRepository<User> GetUserRepository() 
    { 
     var repo = new 
     { 
      Query = Return<IQueryable<User>>.Arguments(() => new[] { 
       new User() { 
        Name = "Jack Sparrow" 
       } 
      }.AsQueryable()) 
     }.ActLike<IRepository<User>>(); 

     return repo; 
    } 
} 

Stosując to podejście, twój beton repozytorium po prostu wdrożyć IRepository<T> interfejs dla każdego T który jest faktycznie rodzajem modelu.

+0

Bardzo ciekawe zalecenie i podejście –

Powiązane problemy