2009-02-03 32 views
13

tôi tự hỏi những gì nó sẽ làm để làm một cái gì đó giống như công việc này:C yêu cầu # Tính năng: thực hiện các giao diện trên các loại vô danh

using System; 

class Program 
{ 
    static void Main() 
    { 
     var f = new IFoo { 
        Foo = "foo", 
        Print =() => Console.WriteLine(Foo) 
      }; 
    } 
} 

interface IFoo 
{ 
    String Foo { get; set; } 
    void Print(); 
} 

Các loại vô danh tạo ra sẽ giống như thế này:

internal sealed class <>f__AnonymousType0<<Foo>j__TPar> : IFoo 
{ 
    readonly <Foo>j__TPar <Foo>i__Field; 

    public <>f__AnonymousType0(<Foo>j__TPar Foo) 
    { 
     this.<Foo>i__Field = Foo; 
    } 

    public <Foo>j__TPar Foo 
    { 
     get { return this.<Foo>i__Field; } 
    } 

    public void Print() 
    { 
     Console.WriteLine(this.Foo); 
    } 
} 

Có lý do gì mà trình biên dịch sẽ không thể làm điều gì đó như thế này? Ngay cả đối với các phương thức không có giá trị hoặc các phương thức lấy tham số thì trình biên dịch có thể suy ra các kiểu từ khai báo giao diện.

Tuyên bố từ chối trách nhiệm: Trong khi tôi nhận ra rằng điều này hiện không khả thi và sẽ có ý nghĩa hơn khi tạo một lớp cụ thể trong trường hợp này tôi quan tâm nhiều hơn đến khía cạnh lý thuyết này.

+0

Ý tưởng này đến tâm trí của tôi ngày hôm qua và tôi đã cố gắng để tạo ra một lớp 'Anonymous 'và sử dụng' Reflection.Emit' để xây dựng như một lớp học thực hiện T. giao diện Nhưng nó tooooo đắt! Sau đó, tôi đã thử vận ​​may của mình trên SO và tìm thấy câu hỏi này, bạn có tiến bộ gì không? –

Trả lời

8

Sẽ có một vài vấn đề với các thành viên quá tải, bộ chỉ mục, và triển khai giao diện rõ ràng.

Tuy nhiên, bạn có thể xác định cú pháp theo cách cho phép bạn giải quyết những vấn đề đó.

Thật thú vị, bạn có thể nhận được khá gần với những gì bạn muốn với C# 3.0 bằng cách viết một thư viện. Về cơ bản, bạn có thể làm điều này:

Create<IFoo> 
(
    new 
    { 
     Foo = "foo", 
     Print = (Action)(() => Console.WriteLine(Foo)) 
    } 
); 

Đó là khá gần với những gì bạn muốn. Sự khác biệt chính là cuộc gọi đến "Tạo" thay vì từ khóa "mới" và thực tế là bạn cần chỉ định loại đại biểu.

Tờ khai "Create" sẽ trông như thế này:

T Create<T> (object o) 
{ 
//... 
} 

Nó sau đó sẽ sử dụng Reflection.Emit để tạo ra một giao diện thực hiện tự động khi chạy.

Cú pháp này, tuy nhiên, không có vấn đề với việc triển khai giao diện rõ ràng và thành viên quá tải, mà bạn không thể giải quyết mà không thay đổi trình biên dịch.

Cách khác là sử dụng trình khởi tạo bộ sưu tập thay vì loại ẩn danh. Điều đó sẽ trông như thế này:

Create 
{ 
    new Members<IFoo> 
    { 
     {"Print", ((IFoo @this)=>Console.WriteLine(Foo))}, 
     {"Foo", "foo"} 
    } 
} 

Điều đó sẽ cho phép bạn:

  1. Xử lý giao diện rõ ràng thực hiện bằng cách xác định một cái gì đó như "IEnumerable.Current" cho tham số chuỗi.
  2. Xác định thành viên.Thêm để bạn không cần phải chỉ định loại đại biểu trong bộ khởi tạo.

Bạn sẽ cần phải làm một vài điều để thực hiện điều này:

  1. Writer một phân tích cú pháp nhỏ cho C# loại tên. Điều này chỉ yêu cầu tên ".", "[]", "<>", ID và tên nguyên thủy, vì vậy bạn có thể làm điều đó trong vài giờ
  2. Triển khai bộ nhớ cache để bạn chỉ tạo một lớp duy nhất cho mỗi giao diện duy nhất
  3. Triển khai biểu tượng Reflection.Emit code. Điều này có thể mất khoảng 2 ngày nhiều nhất.
+0

Điều này rất nhiều trong tinh thần của câu hỏi - cảm ơn! –

-1

Điều này hiện không khả thi.

Điều gì sẽ là sự khác biệt giữa điều này và chỉ đơn giản là làm cho IFoo một lớp bê tông thay thế? Có vẻ như đó có thể là lựa chọn tốt hơn.

Điều gì sẽ mất? Một trình biên dịch mới và tấn kiểm tra để đảm bảo họ không phá vỡ các tính năng khác. Cá nhân, tôi nghĩ sẽ dễ dàng hơn khi yêu cầu các nhà phát triển tạo ra một phiên bản cụ thể của lớp học của họ.

2

Miễn là chúng tôi đưa ra một danh sách mong muốn giao diện, tôi thực sự muốn có thể nói với trình biên dịch rằng một lớp thực hiện một giao diện bên ngoài định nghĩa lớp - ngay cả trong một hội đồng riêng biệt.

Ví dụ: giả sử tôi đang làm việc trên một chương trình để trích xuất tệp từ các định dạng lưu trữ khác nhau. Tôi muốn có thể lấy các cài đặt hiện tại từ các thư viện khác nhau — nói, SharpZipLib và triển khai PGP thương mại — và tiêu thụ cả hai thư viện bằng cùng một mã mà không tạo các lớp mới. Sau đó, tôi có thể sử dụng các kiểu từ một trong hai nguồn trong các ràng buộc chung.

Sử dụng khác sẽ cho trình biên dịch biết rằng System.Xml.Serialization.XmlSerializer thực hiện giao diện System.Runtime.Serialization.IFormatter (đã có, nhưng trình biên dịch không biết).

Điều này cũng có thể được sử dụng để thực hiện yêu cầu của bạn, không tự động. Bạn vẫn phải nói rõ với trình biên dịch về nó. Bạn không chắc chắn về cách cú pháp sẽ trông như thế nào, bởi vì bạn vẫn phải tự vẽ các phương thức và thuộc tính một cách thủ công ở đâu đó, điều đó có nghĩa là rất nhiều sự phân biệt. Có thể một cái gì đó tương tự như phương pháp mở rộng.

+0

huh? "Nó đã làm, nhưng trình biên dịch không biết nó" 1. Không có nó không. 2. Nó thực hiện Serialize/Deserialize, nhưng không phải là ba thuộc tính Binder, Context, SurrogateSelector. –

+0

Eh: bạn có thể lắc nó trong khá dễ dàng. Nó _should_ đã được xây dựng để bạn có thể có một phương thức chấp nhận cá thể IFormatter và truyền BinaryFormatter, SoapFormatter, XmlSerializer hiện có, hoặc thực thi IFormatter của riêng bạn mà không cần khiếu nại. –

+0

Bạn có biết rằng Oxygene [có điều này] (http://prismwiki.codegear.com/en/Provide_Mixin-like_functionality)? Tôi cũng đã mô tả một tính năng mà tôi muốn xem [ở đây] (http://codecrafter.blogspot.com/2010/10/roles-in-c.html). –

4

Không thể tạo loại ẩn danh để thực hiện bất kỳ điều gì ngoại trừ có thuộc tính chỉ đọc.

Trích dẫn các C# Programming Guide (Anonymous Types):

"loại Anonymous là loại lớp đó bao gồm một hoặc của cộng đồng hơn thuộc tính read-only Không loại khác của các thành viên lớp như phương pháp hoặc sự kiện được cho phép.. Loại ẩn danh không thể truyền sang bất kỳ giao diện nào hoặc loại ngoại trừ đối tượng. "

+3

Có, nhưng tôi hy vọng sẽ sửa lại định nghĩa đó :) –

0

Ý tưởng thú vị, tôi muốn được một chút lo ngại rằng ngay cả khi nó có thể được thực hiện nó có thể gây nhầm lẫn. Ví dụ.khi định nghĩa một thuộc tính với các bộ định cư không nhỏ và các getters, hoặc cách phân biệt Foo nếu kiểu khai báo cũng chứa một thuộc tính được gọi là Foo.

Tôi tự hỏi liệu điều này có dễ dàng hơn trong ngôn ngữ năng động hơn hay thậm chí với loại động và DLR trong C# 4.0 không?

Có lẽ ngày hôm nay trong C# một số mục đích có thể đạt được với lambdas:

void Main() { 
    var foo = new Foo(); 
    foo.Bar = "bar"; 
    foo.Print =() => Console.WriteLine(foo.Bar); 
    foo.Print(); 
} 


class Foo : IFoo { 
    public String Bar { get; set; }  
    public Action Print {get;set;} 
} 
1

Bạn có thể có một cái gì đó giống như anonymous classes trong Java:

using System; 

class Program { 
    static void Main() { 
    var f = new IFoo() { 
     public String Foo { get { return "foo"; } } 
     public void Print() { Console.WriteLine(Foo); } 
    }; 
    } 
} 

interface IFoo { 
    String Foo { get; set; } 
    void Print(); 
} 
-1

Tôi đã sử dụng trong Java các Amonimous lớp thông qua "IFoo mới() {...}" Sintax và đó là thực tế và dễ dàng khi bạn phải thực hiện nhanh một giao diện đơn giản.

Là một mẫu nó sẽ được tốt đẹp để thực hiện IDisposable cách này trên di sản đối tượng sử dụng chỉ một lần thay vì phát sinh một lớp mới để thực hiện nó.

6

nó đòi hỏi C# 4, nhưng khuôn khổ opensource impromptu interface có thể giả mạo này ra khỏi hộp bằng DLR proxy nội bộ. việc thực hiện là tốt mặc dù không tốt như nếu thay đổi mà bạn đề xuất tồn tại.

using ImpromptuInterface.Dynamic; 

...

var f = ImpromptuGet.Create<IFoo>(new{ 
       Foo = "foo", 
       Print = ReturnVoid.Arguments(() => Console.WriteLine(Foo)) 
      }); 
1

Điều này sẽ không hay. Lớp ẩn danh nội tuyến:

List<Student>.Distinct(new IEqualityComparer<Student>() 
{ 
    public override bool Equals(Student x, Student y) 
    { 
     return x.Id == y.Id; 
    } 

    public override int GetHashCode(Student obj) 
    { 
     return obj.Id.GetHashCode(); 
    } 
}) 
1

Tôi sẽ đổ kết quả này tại đây. Tôi đã viết nó một lúc trước nhưng IIRC nó hoạt động OK.

Đầu tiên là chức năng trợ giúp để chụp MethodInfo và trả về số Type của số Func hoặc Action phù hợp. Bạn cần một chi nhánh cho mỗi số tham số, thật không may, và tôi dường như dừng lại ở ba.

static Type GenerateFuncOrAction(MethodInfo method) 
{ 
    var typeParams = method.GetParameters().Select(p => p.ParameterType).ToArray(); 
    if (method.ReturnType == typeof(void)) 
    { 
     if (typeParams.Length == 0) 
     { 
      return typeof(Action); 
     } 
     else if (typeParams.Length == 1) 
     { 
      return typeof(Action<>).MakeGenericType(typeParams); 
     } 
     else if (typeParams.Length == 2) 
     { 
      return typeof(Action<,>).MakeGenericType(typeParams); 
     } 
     else if (typeParams.Length == 3) 
     { 
      return typeof(Action<,,>).MakeGenericType(typeParams); 
     } 
     throw new ArgumentException("Only written up to 3 type parameters"); 
    } 
    else 
    { 
     if (typeParams.Length == 0) 
     { 
      return typeof(Func<>).MakeGenericType(typeParams.Concat(new[] { method.ReturnType }).ToArray()); 
     } 
     else if (typeParams.Length == 1) 
     { 
      return typeof(Func<,>).MakeGenericType(typeParams.Concat(new[] { method.ReturnType }).ToArray()); 
     } 
     else if (typeParams.Length == 2) 
     { 
      return typeof(Func<,,>).MakeGenericType(typeParams.Concat(new[] { method.ReturnType }).ToArray()); 
     } 
     else if (typeParams.Length == 3) 
     { 
      return typeof(Func<,,,>).MakeGenericType(typeParams.Concat(new[] { method.ReturnType }).ToArray()); 
     } 
     throw new ArgumentException("Only written up to 3 type parameters"); 
    } 
} 

Và bây giờ là phương pháp mà có một giao diện như một tham số chung và trả về một Type mà thực hiện giao diện và có một constructor (cần phải được gọi qua Activator.CreateInstance) tham gia một Func hoặc Action cho mỗi phương pháp/getter/setter. Bạn cần phải biết đúng thứ tự để đặt chúng trong constructor, mặc dù. Ngoài ra (mã nhận xét) nó có thể tạo ra một DLL mà sau đó bạn có thể tham khảo và sử dụng loại trực tiếp.

static Type GenerateInterfaceImplementation<TInterface>() 
{ 
    var interfaceType = typeof(TInterface); 
    var funcTypes = interfaceType.GetMethods().Select(GenerateFuncOrAction).ToArray(); 

    AssemblyName aName = 
     new AssemblyName("Dynamic" + interfaceType.Name + "WrapperAssembly"); 
    var assBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(
      aName, 
      AssemblyBuilderAccess.Run/*AndSave*/); // to get a DLL 

    var modBuilder = assBuilder.DefineDynamicModule(aName.Name/*, aName.Name + ".dll"*/); // to get a DLL 

    TypeBuilder typeBuilder = modBuilder.DefineType(
     "Dynamic" + interfaceType.Name + "Wrapper", 
      TypeAttributes.Public); 

    // Define a constructor taking the same parameters as this method. 
    var ctrBuilder = typeBuilder.DefineConstructor(
     MethodAttributes.Public | MethodAttributes.HideBySig | 
      MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, 
     CallingConventions.Standard, 
     funcTypes); 


    // Start building the constructor. 
    var ctrGenerator = ctrBuilder.GetILGenerator(); 
    ctrGenerator.Emit(OpCodes.Ldarg_0); 
    ctrGenerator.Emit(
     OpCodes.Call, 
     typeof(object).GetConstructor(Type.EmptyTypes)); 

    // For each interface method, we add a field to hold the supplied 
    // delegate, code to store it in the constructor, and an 
    // implementation that calls the delegate. 
    byte methodIndex = 0; 
    foreach (var interfaceMethod in interfaceType.GetMethods()) 
    { 
     ctrBuilder.DefineParameter(
      methodIndex + 1, 
      ParameterAttributes.None, 
      "del_" + interfaceMethod.Name); 

     var delegateField = typeBuilder.DefineField(
      "del_" + interfaceMethod.Name, 
      funcTypes[methodIndex], 
      FieldAttributes.Private); 

     ctrGenerator.Emit(OpCodes.Ldarg_0); 
     ctrGenerator.Emit(OpCodes.Ldarg_S, methodIndex + 1); 
     ctrGenerator.Emit(OpCodes.Stfld, delegateField); 

     var metBuilder = typeBuilder.DefineMethod(
      interfaceMethod.Name, 
      MethodAttributes.Public | MethodAttributes.Virtual | 
       MethodAttributes.Final | MethodAttributes.HideBySig | 
       MethodAttributes.NewSlot, 
      interfaceMethod.ReturnType, 
      interfaceMethod.GetParameters() 
       .Select(p => p.ParameterType).ToArray()); 

     var metGenerator = metBuilder.GetILGenerator(); 
     metGenerator.Emit(OpCodes.Ldarg_0); 
     metGenerator.Emit(OpCodes.Ldfld, delegateField); 

     // Generate code to load each parameter. 
     byte paramIndex = 1; 
     foreach (var param in interfaceMethod.GetParameters()) 
     { 
      metGenerator.Emit(OpCodes.Ldarg_S, paramIndex); 
      paramIndex++; 
     } 
     metGenerator.EmitCall(
      OpCodes.Callvirt, 
      funcTypes[methodIndex].GetMethod("Invoke"), 
      null); 

     metGenerator.Emit(OpCodes.Ret); 
     methodIndex++; 
    } 

    ctrGenerator.Emit(OpCodes.Ret); 

    // Add interface implementation and finish creating. 
    typeBuilder.AddInterfaceImplementation(interfaceType); 
    var wrapperType = typeBuilder.CreateType(); 
    //assBuilder.Save(aName.Name + ".dll"); // to get a DLL 

    return wrapperType; 
} 

Bạn có thể sử dụng điều này như ví dụ:

public interface ITest 
{ 
    void M1(); 
    string M2(int m2, string n2); 
    string prop { get; set; } 

    event test BoopBooped; 
} 

Type it = GenerateInterfaceImplementation<ITest>(); 
ITest instance = (ITest)Activator.CreateInstance(it, 
    new Action(() => {Console.WriteLine("M1 called"); return;}), 
    new Func<int, string, string>((i, s) => "M2 gives " + s + i.ToString()), 
    new Func<String>(() => "prop value"), 
    new Action<string>(s => {Console.WriteLine("prop set to " + s);}), 
    new Action<test>(eh => {Console.WriteLine(eh("handler added"));}), 
    new Action<test>(eh => {Console.WriteLine(eh("handler removed"));})); 

// or with the generated DLL 
ITest instance = new DynamicITestWrapper(
    // parameters as before but you can see the signature 
    ); 
Các vấn đề liên quan