匿名类可以实现接口吗?

Posted

技术标签:

【中文标题】匿名类可以实现接口吗?【英文标题】:Can anonymous class implement interface? 【发布时间】:2010-09-16 12:02:54 【问题描述】:

是否可以让匿名类型实现接口?

我有一段代码想要使用,但不知道如何操作。

我有几个答案,要么说不,要么创建一个实现接口的类,构造它的新实例。这不是很理想,但我想知道是否有一种机制可以在接口之上创建一个精简的动态类,这会使这变得简单。

public interface DummyInterface

    string A  get; 
    string B  get; 


public class DummySource

    public string A  get; set; 
    public string C  get; set; 
    public string D  get; set; 


public class Test

    public void WillThisWork()
    
        var source = new DummySource[0];
        var values = from value in source
                     select new
                     
                         A = value.A,
                         B = value.C + "_" + value.D
                     ;

        DoSomethingWithDummyInterface(values);

    

    public void DoSomethingWithDummyInterface(IEnumerable<DummyInterface> values)
    
        foreach (var value in values)
        
            Console.WriteLine("A = '0', B = '1'", value.A, value.B);
        
    

我发现一篇文章 Dynamic interface wrapping 描述了一种方法。这是最好的方法吗?

【问题讨论】:

链接似乎已过期,这可能是一个合适的替代方案liensberger.it/web/blog/?p=298。 是的,您可以使用 .NET 4 及更高版本(通过 DLR)执行此操作,使用 ImpromptuInterface nuget 包。 @PhilCooper 您的链接已关闭,可能至少从 2016 年开始 - 但幸运的是它在那之前已存档。 web.archive.org/web/20111105150920/http://www.liensberger.it/… 在 C#9 中你可以使用记录 (devblogs.microsoft.com/dotnet/welcome-to-c-9-0) 我知道这不一样但也是实现“简单”接口的一种非常轻量级的方法:) 【参考方案1】:

不,匿名类型不能实现接口。来自C# programming guide:

匿名类型是由一个或多个公共只读属性组成的类类型。不允许使用其他类型的类成员,例如方法或事件。匿名类型不能转换为除对象之外的任何接口或类型。

【讨论】:

无论如何都会很高兴拥有这些东西。如果您在谈论代码可读性,那么 lambda 表达式通常不是要走的路。如果我们在谈论 RAD,我完全喜欢类似 java 的匿名接口实现。顺便说一句,在某些情况下,该功能比委托更强大 @ArsenZahray:lambda 表达式,如果使用得当,实际上会增加代码的可读性。它们在函数链中使用时特别强大,可以减少或消除对局部变量的需求。 你可以这样做“匿名实现类 - C#的设计模式” - twistedoakstudios.com/blog/… @DmitryPavlov,这非常有价值。路人:here是精简版。 您可以将匿名类型强制转换为具有相同字段的匿名对象***.com/questions/1409734/cast-to-anonymous-type【参考方案2】:

虽然线程中的答案都足够真实,但我还是忍不住想告诉你,实际上有可能让匿名类实现接口,尽管这需要一点时间到那里的创造性作弊。

早在 2008 年,我正在为我当时的雇主编写一个自定义 LINQ 提供程序,有一次我需要能够将“我的”匿名类与其他匿名类区分开来,这意味着让它们实现一个我可以使用的接口键入检查它们。我们解决它的方法是使用方面(我们使用PostSharp),直接在IL 中添加接口实现。所以,事实上,让匿名类实现接口是可行的,你只需要稍微改变一下规则就可以了。

【讨论】:

@Gusdor,在这种情况下,我们可以完全控制构建,并且它始终在专用机器上运行。此外,由于我们使用的是 PostSharp,而且我们所做的事情在该框架内是完全合法的,所以只要我们确保 PostSharp 安装在我们使用的构建服务器上,什么都不会真正流行。 @Gusdor 我同意其他程序员应该很容易获得项目并在没有很大困难的情况下进行编译,但这是一个可以单独解决的不同问题,无需完全避免使用工具或框架,如 postsharp。您可以针对 VS 本身或不属于 C# 规范的任何其他非标准 MS 框架提出相同的论点。你需要这些东西,否则它会“流行起来”。这不是海事组织的问题。问题是当构建变得如此复杂以至于很难让这些东西一起正常工作时。 @ZainRizvi 不,它没有。据我所知,它仍在生产中。当时提出的担忧对我来说似乎很奇怪,现在对我来说充其量是武断的。它基本上是在说“不要使用框架,事情会崩溃!”。他们没有,什么都没有流行,我并不感到惊讶。 现在容易多了;代码生成后无需修改IL;只需使用ImpromptuInterface。 -- 它允许您将任何对象(包括匿名类型的对象)绑定到任何接口(当然,如果您尝试使用该类实际上不支持的接口的一部分,则会出现后期绑定异常)。【参考方案3】:

将匿名类型转换为接口一直是我一直想要的,但不幸的是,当前的实现迫使您必须实现该接口。

最好的解决方案是使用某种类型的动态代理来为您创建实现。使用优秀的LinFu project可以替换

select new

  A = value.A,
  B = value.C + "_" + value.D
;

 select new DynamicObject(new
 
   A = value.A,
   B = value.C + "_" + value.D
 ).CreateDuck<DummyInterface>();

【讨论】:

Impromptu-Interface Project 将在 .NET 4.0 中使用 DLR 执行此操作,并且比 Linfu 更轻。 DynamicObject是林福型的吗? System.Dynamic.DynamicObject 只有一个受保护的构造函数(至少在 .NET 4.5 中)。 是的。我指的是早于 DLR 版本的DynamicObject 的 LinFu 实现【参考方案4】:

匿名类型可以通过动态代理实现接口。

我在GitHub 上写了一个扩展方法,并在http://wblo.gs/feE 上写了一篇博文来支持这种情况。

方法可以这样使用:

class Program

    static void Main(string[] args)
    
        var developer = new  Name = "Jason Bowers" ;

        PrintDeveloperName(developer.DuckCast<IDeveloper>());

        Console.ReadKey();
    

    private static void PrintDeveloperName(IDeveloper developer)
    
        Console.WriteLine(developer.Name);
    


public interface IDeveloper

    string Name  get; 

【讨论】:

【参考方案5】:

没有;匿名类型除了具有一些属性外,不能做任何事情。您将需要创建自己的类型。我没有深入阅读链接的文章,但看起来它使用 Reflection.Emit 动态创建新类型;但是如果你将讨论限制在C# 本身,你就不能做你想做的事。

【讨论】:

重要的是要注意:属性也可能包括函数或空心(Action):select new ... MyFunction = new Func(s => value.A == s ) 虽然你不能在你的函数中引用新的属性(我们不能用“A”代替“value.A”)。 嗯,这不只是一个恰好是委托的属性吗?它实际上不是一种方法。 我使用 Reflection.Emit 在运行时创建类型,但相信现在我更喜欢 AOP 解决方案来避免运行时成本。【参考方案6】:

最好的解决方案就是不要使用匿名类。

public class Test

    class DummyInterfaceImplementor : IDummyInterface
    
        public string A  get; set; 
        public string B  get; set; 
    

    public void WillThisWork()
    
        var source = new DummySource[0];
        var values = from value in source
                     select new DummyInterfaceImplementor()
                     
                         A = value.A,
                         B = value.C + "_" + value.D
                     ;

        DoSomethingWithDummyInterface(values.Cast<IDummyInterface>());

    

    public void DoSomethingWithDummyInterface(IEnumerable<IDummyInterface> values)
    
        foreach (var value in values)
        
            Console.WriteLine("A = '0', B = '1'", value.A, value.B);
        
    

请注意,您需要将查询结果转换为接口的类型。可能有更好的方法,但我找不到。

【讨论】:

您可以使用values.OfType&lt;IDummyInterface&gt;() 代替演员表。它只返回集合中实际上可以转换为该类型的对象。这一切都取决于你想要什么。【参考方案7】:

具体问题的答案是否定的。但是您是否一直在研究模拟框架?我使用最小起订量,但那里有数百万个,它们允许您在线实现/存根(部分或全部)接口。例如。

public void ThisWillWork()

    var source = new DummySource[0];
    var mock = new Mock<DummyInterface>();

    mock.SetupProperty(m => m.A, source.Select(s => s.A));
    mock.SetupProperty(m => m.B, source.Select(s => s.C + "_" + s.D));

    DoSomethingWithDummyInterface(mock.Object);

【讨论】:

【参考方案8】:

另一种选择是创建一个具体的实现类,该类在构造函数中采用 lambda。

public interface DummyInterface

    string A  get; 
    string B  get; 


// "Generic" implementing class
public class Dummy : DummyInterface

    private readonly Func<string> _getA;
    private readonly Func<string> _getB;

    public Dummy(Func<string> getA, Func<string> getB)
    
        _getA = getA;
        _getB = getB;
    

    public string A => _getA();

    public string B => _getB();


public class DummySource

    public string A  get; set; 
    public string C  get; set; 
    public string D  get; set; 


public class Test

    public void WillThisWork()
    
        var source = new DummySource[0];
        var values = from value in source
                     select new Dummy // Syntax changes slightly
                     (
                         getA: () => value.A,
                         getB: () => value.C + "_" + value.D
                     );

        DoSomethingWithDummyInterface(values);

    

    public void DoSomethingWithDummyInterface(IEnumerable<DummyInterface> values)
    
        foreach (var value in values)
        
            Console.WriteLine("A = '0', B = '1'", value.A, value.B);
        
    

如果您要做的只是将DummySource 转换为DummyInterface,那么只有一个在构造函数中采用DummySource 并实现接口的类会更简单。

但是,如果您需要将许多类型转换为 DummyInterface,那么样板代码就少得多了。

【讨论】:

【参考方案9】:

使用 Roslyn,您可以动态创建从接口(或抽象类)继承的类。

我使用以下内容从抽象类创建具体类。

在本例中,AAnimal 是一个抽象类。

var personClass = typeof(AAnimal).CreateSubclass("Person");

然后你可以实例化一些对象:

var person1 = Activator.CreateInstance(personClass);
var person2 = Activator.CreateInstance(personClass);

毫无疑问,这并不适用于所有情况,但应该足以让您开始:

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;

namespace Publisher

    public static class Extensions
    
        public static Type CreateSubclass(this Type baseType, string newClassName, string newNamespace = "Magic")
        
            //todo: handle ref, out etc.
            var concreteMethods = baseType
                                    .GetMethods()
                                    .Where(method => method.IsAbstract)
                                    .Select(method =>
                                    
                                        var parameters = method
                                                            .GetParameters()
                                                            .Select(param => $"param.ParameterType.FullName param.Name")
                                                            .ToString(", ");

                                        var returnTypeStr = method.ReturnParameter.ParameterType.Name;
                                        if (returnTypeStr.Equals("Void")) returnTypeStr = "void";

                                        var methodString = @$"
                                        public override returnTypeStr method.Name(parameters)
                                        
                                            Console.WriteLine(""newNamespace.newClassName.method.Name() was called"");
                                        ";

                                        return methodString.Trim();
                                    )
                                    .ToList();

            var concreteMethodsString = concreteMethods
                                        .ToString(Environment.NewLine + Environment.NewLine);

            var classCode = @$"
            using System;

            namespace newNamespace
            
                public class newClassName: baseType.FullName
                
                    public newClassName()
                    
                    

                    concreteMethodsString
                
            
            ".Trim();

            classCode = FormatUsingRoslyn(classCode);


            /*
            var assemblies = new[]
            
                MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
                MetadataReference.CreateFromFile(baseType.Assembly.Location),
            ;
            */

            var assemblies = AppDomain
                        .CurrentDomain
                        .GetAssemblies()
                        .Where(a => !string.IsNullOrEmpty(a.Location))
                        .Select(a => MetadataReference.CreateFromFile(a.Location))
                        .ToArray();

            var syntaxTree = CSharpSyntaxTree.ParseText(classCode);

            var compilation = CSharpCompilation
                                .Create(newNamespace)
                                .AddSyntaxTrees(syntaxTree)
                                .AddReferences(assemblies)
                                .WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));

            using (var ms = new MemoryStream())
            
                var result = compilation.Emit(ms);
                //compilation.Emit($"C:\\Temp\\newNamespace.dll");

                if (result.Success)
                
                    ms.Seek(0, SeekOrigin.Begin);
                    Assembly assembly = Assembly.Load(ms.ToArray());

                    var newTypeFullName = $"newNamespace.newClassName";

                    var type = assembly.GetType(newTypeFullName);
                    return type;
                
                else
                
                    IEnumerable<Diagnostic> failures = result.Diagnostics.Where(diagnostic =>
                        diagnostic.IsWarningAsError ||
                        diagnostic.Severity == DiagnosticSeverity.Error);

                    foreach (Diagnostic diagnostic in failures)
                    
                        Console.Error.WriteLine("0: 1", diagnostic.Id, diagnostic.GetMessage());
                    

                    return null;
                
            
        

        public static string ToString(this IEnumerable<string> list, string separator)
        
            string result = string.Join(separator, list);
            return result;
        

        public static string FormatUsingRoslyn(string csCode)
        
            var tree = CSharpSyntaxTree.ParseText(csCode);
            var root = tree.GetRoot().NormalizeWhitespace();
            var result = root.ToFullString();
            return result;
        
    

【讨论】:

以上是关于匿名类可以实现接口吗?的主要内容,如果未能解决你的问题,请参考以下文章

接口作为成员变量——实现类和匿名内部类和匿名对象

Java中,匿名内部类

匿名内部类

Java匿名内部类和Lambda表达式

匿名内部类是一种特殊的局部内部类,它是通过匿名类实现接口

Javase学习09-匿名内部类