анонимные классы, proof of concept
От: Vermicious Knid  
Дата: 26.03.07 14:23
Оценка: 35 (4)
Очень далекий от идеала вариант, не вся необходимая функциональность реализована, есть несколько шероховатых моментов. На правах чернового наброска(код без комментариев и довольно неопрятный).

Для начала код теста:
using System.Console;
using Nemerle.Extensions;

[Record] public class Foobar[T]
{
    public mutable n : T;
    public override ToString() : string { $"Foobar($n)" }
    public override GetHashCode() : int { n.GetHashCode() }
}

def foo = new { Foo = 1; Bar = "foobar" + 2.ToString(); Baz =  Foobar(22) };
def foo2 = new { Foo = 1; Bar = "foobar2"; Baz = Foobar(22) };
def bar = new { It = 1; Is = 2.0; Crazy = new { Really = 1; Crazy = "2"; Stuff = 3.0 } };

def dump(k, v : object)
{
    WriteLine($"$(k.PadRight(40, ' ')) : $v")
}
dump("foo", foo);
dump("foo.GetHashCode() == foo2.GetHashCode()",foo.GetHashCode()==foo2.GetHashCode());
dump("foo.GetType() == foo2.GetType()", foo.GetType().Equals(foo2.GetType()));
dump("bar.__Fields", bar.__Fields);
dump("bar.__Values", bar.__Values);
dump("bar.Crazy.__Fields", bar.Crazy.__Fields);
WriteLine();

def test_stuff(x : Anonymous[int*string*Foobar[int]])
{
    WriteLine($"calling 'test_stuff' on $x");
    match(x)
    {
        | (__Fields = [a,b,c], __Values = (x,y,z)) =>
            WriteLine($"$a = $x");
            WriteLine($"$b = $y");
            WriteLine($"$c = $z");
        | _ => ()
    }
}
test_stuff(foo)


Вот это выводится в консоль при его выполнении:
foo                                      : [Foo, Bar, Baz] = (1, foobar2, Foobar(22))
foo.GetHashCode() == foo2.GetHashCode()  : True
foo.GetType() == foo2.GetType()          : True
bar.__Fields                             : [It, Is, Crazy]
bar.__Values                             : (1, 2, [Really, Crazy, Stuff] = (1, 2, 3))
bar.Crazy.__Fields                       : [Really, Crazy, Stuff]

calling 'test_stuff' on [Foo, Bar, Baz] = (1, foobar2, Foobar(22))
Foo = 1
Bar = foobar2
Baz = Foobar(22)


Теперь сам код макроса. Предупреждаю сразу, что у меня не самая новая версия компилятора, но значительно новее последнего публичного релиза. Возможно будут проблемы. Используется indentation based syntax, поэтому возможно лучше(во избежание проблем с компиляцией) скачать в виде файла(ссылка на архив в начале сообщения).
#pragma indent
using System
using Nemerle.Compiler
using Nemerle.Collections
using Nemerle.Utility

set namespace Nemerle.Extensions
    
public abstract class Anonymous[T]
    public abstract __Fields : list[string] { get; }
    public abstract __Values : T { get; }

    public override GetHashCode() : int
        __Values.GetHashCode() ^ __Fields.GetHashCode()

    public override ToString() : string
        $"$__Fields = $__Values"

module StringFilterExt
    public Filter(this str : string, pred : char->bool) : string
        def sb = Text.StringBuilder(str.Length)
        foreach(c when pred(c) in str)
            _ = sb.Append(c)
        sb.ToString()

macro CreateAnonymousClass(body) syntax("new", body)
    def typer = Macros.ImplicitCTX()

    def exprs =
        match(body)
            | <[{.. $exprs }]> => exprs
            | _ => Message.FatalError(body.Location, $"expected new { Name1 = expr1, ... }; got $body")

    def exprs = exprs.Map(
        _ => {
            | <[ $(field : name) = $value ]> => (field, value)
            | expr => Message.FatalError(expr.Location, $"expected Name = expr1, got $expr")})
    
    def (fields, values) = List.Split(exprs)

    def values = <[ (.. $values) ]> // build a tuple expression
    def tvalues = typer.TypeExpr(values)

    def buildAnonymousClass(fail_loud)
        match (tvalues.Type.Hint)
            | Some(Tuple(args) as t) =>
                def t = t.DeepFix()


                def members = List.Combine(fields, args).Map(
                    (field, typ) =>
                        <[ decl: [Accessor(flags=WantSetter)] mutable $(field.NewName("_" + field.Id) : name) : $(typ : typed)]>)

                def fields_strings = fields.Map(f => <[ $(f.Id : string) ]>)
                def members = <[ decl: public override __Fields : list[string] { get { [.. $fields_strings] } } ]> :: members


                def fields_access_exprs = fields.Map(f => <[ $(f : name) ]>)
                def members = <[ decl: public override __Values : $(t : typed) { get { (.. $fields_access_exprs) }  } ]> :: members

                def mangled_args = t.ToString().Filter(c => Char.IsLetterOrDigit(c) || c == '_')
                def mangled_name = $"Anonymous_$(mangled_args)_$(t.ToString().GetHashCode())"

                unless(typer.Env.LookupType([mangled_name]) is Some(_))
                    def anonyclass =
                        <[ decl: [Record] public class $(mangled_name : usesite) : Anonymous[$(t : typed)] { .. $members }]>
                    def tbuilder = typer.Env.Define(anonyclass)
                    unless(typer.InErrorMode) tbuilder.Compile()

                Some(<[$(mangled_name : usesite)($values)]>)

            | _ =>
                when(fail_loud)
                    Message.Error(body.Location, "compiler was unable to analyze types of expressions inside anonymous type")
                None()
                    
    typer.DelayMacro(buildAnonymousClass)


P.S. Единственная замеченная серьезная проблема с этим кодом — он не совсем правильно работает с отложенным выводом типов, хотя я изначально использовал DelayMacro и надеялся, что этого будет достаточно. Оказалось недостаточно — если тип полей анонимного класса на момент выполнения макроса известен не полностью, то вместо неизвестных type variables он подставляет System.Object, что конечно неприятно. Единственный выход это наверное откладывать выполнение макроса до тех пор пока все типы полей анонимного класса не станут известны. Как это сделать надо еще подумать или спросить у Москаля/Скальски.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.