【问题标题】:Is it possible to use a c# object initializer with a factory method?是否可以将 c# 对象初始化程序与工厂方法一起使用?
【发布时间】:2010-10-15 02:28:07
【问题描述】:

我有一个带有静态工厂方法的类。我想调用工厂来检索类的实例,然后进行额外的初始化,最好通过 c# 对象初始化语法:

MyClass instance = MyClass.FactoryCreate()
{
  someProperty = someValue;
}

对比

MyClass instance = MyClass.FactoryCreate();
instance.someProperty = someValue;

【问题讨论】:

  • 希望 C# 为像这样的静态“创建”方法添加一些糖(就像他们为集合所做的例如“添加”):)

标签: c# object factory initializer


【解决方案1】:

没有。或者,您可以接受 lambda 作为参数,这也使您可以完全控制将调用“创建”过程的哪一部分。这样你就可以这样称呼它:

MyClass instance = MyClass.FactoryCreate(c=>
   {
       c.SomeProperty = something;
       c.AnotherProperty = somethingElse;
   });

创建看起来类似于:

public static MyClass FactoryCreate(Action<MyClass> initalizer)
{
    MyClass myClass = new MyClass();
    //do stuff
    initializer( myClass );
    //do more stuff
    return myClass;
}

另一种选择是返回一个构建器(使用隐式转换运算符到 MyClass)。你会这样称呼:

MyClass instance = MyClass.FactoryCreate()
   .WithSomeProperty(something)
   .WithAnotherProperty(somethingElse);

检查this 的建造者

这两个版本都在编译时进行检查,并具有完整的智能感知支持。


需要默认构造函数的第三个选项:

//used like:
var data = MyClass.FactoryCreate(() => new Data
{
    Desc = "something",
    Id = 1
});
//Implemented as:
public static MyClass FactoryCreate(Expression<Func<MyClass>> initializer)
{
    var myclass = new MyClass();
    ApplyInitializer(myclass, (MemberInitExpression)initializer.Body);
    return myclass ;
}
//using this:
static void ApplyInitializer(object instance, MemberInitExpression initalizer)
{
    foreach (var bind in initalizer.Bindings.Cast<MemberAssignment>())
    {
        var prop = (PropertyInfo)bind.Member;
        var value = ((ConstantExpression)bind.Expression).Value;
        prop.SetValue(instance, value, null);
    }
}

它介于编译时检查和未检查之间。它确实需要一些工作,因为它在分配上强制使用常量表达式。我认为其他任何事情都是答案中已经存在的方法的变体。请记住,您也可以使用普通作业,请考虑您是否真的需要其中任何一个。

【讨论】:

  • 我喜欢 lambda 解决方案,它有点接近正确的语法,但构建器语法要求我为每个属性创建一个函数,这是不可行的,尤其是在抽象工厂情况下。
  • @Gaijin 我同意,lambda 是一种非常快速、良好且支持良好的方式。我将构建器用于具有明确默认值的测试代码和一些不只是设置属性的方法。特别是如果 MyClass 是不可变的(因为你需要在构造函数中应用它),它真的很有用。
  • @Gaijin 发布了第三个版本,并附有一条评论,提醒您也可以进行正常作业:)
【解决方案2】:

您可以使用如下扩展方法:

namespace Utility.Extensions
{
    public static class Generic
    {
        /// <summary>
        /// Initialize instance.
        /// </summary>
        public static T Initialize<T>(this T instance, Action<T> initializer)
        {
            initializer(instance);
            return instance;
        }
    }
}

你可以这样称呼它:

using Utility.Extensions;
// ...
var result = MyClass.FactoryCreate()
                .Initialize(x =>
                {
                    x.someProperty = someValue;
                    x.someProperty2 = someValue2;
                });

【讨论】:

    【解决方案3】:

    是的。您可以通过以下技巧为已创建的实例使用对象初始化器。您应该创建一个简单的对象包装器:

    public struct ObjectIniter<TObject>
    {
        public ObjectIniter(TObject obj)
        {
            Obj = obj;
        }
    
        public TObject Obj { get; }
    }
    

    现在您可以像这样使用它来初始化您的对象:

    new ObjectIniter<MyClass>(existingInstance)
    {
        Obj =
        {
            //Object initializer of MyClass:
            Property1 = value1,
            Property2 = value2,
            //...
        }
    };
    

    附: dotnet 仓库中的相关讨论: https://github.com/dotnet/csharplang/issues/803

    【讨论】:

    • 你甚至需要在用法中指定 吗?不能从传入的对象中推断出来吗?
    • 不幸的是,它是必需的。整个技巧是基于将现有实例包装到构造函数中。而且你不能在构造函数中使用类型推断。
    【解决方案4】:

    “否”+1。

    这是匿名对象方式的替代方法:

    var instance = MyClass.FactoryCreate(
        SomeProperty => "Some value",
        OtherProperty => "Other value");
    

    在这种情况下,FactoryCreate() 将类似于:

    public static MyClass FactoryCreate(params Func<object, object>[] initializers)
    {
        var result = new MyClass();
        foreach (var init in initializers) 
        {
            var name = init.Method.GetParameters()[0].Name;
            var value = init(null);
            typeof(MyClass)
                .GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase)
                .SetValue(result, value, null);
        }
        return result;
    }
    

    【讨论】:

    • 不错!如果您对 Expression> 执行相同的操作,消除重构的需要,会不会更快?
    • 回答我自己:不,不会。它会导致我们每次都编译()表达式。 Benchmark 说它慢了 30 倍以上……
    【解决方案5】:

    不,对象初始化器只能用于构造函数对“new”的调用。一种选择可能是向您的工厂方法添加一些额外的参数,以便在工厂内创建对象时设置这些值。

    MyClass instance = MyClass.FactoryCreate(int someValue, string otherValue);
    

    【讨论】:

      【解决方案6】:

      就像大家说的那样,不。

      已经建议使用 lambda 作为参数。
      更优雅的方法是接受匿名并根据对象设置属性。即

      MyClass instance = MyClass.FactoryCreate(new {
          SomeProperty = someValue,
          OtherProperty = otherValue
      });
      

      但这会慢得多,因为所有属性都必须反映对象。

      【讨论】:

        【解决方案7】:

        不,这是您只能“内联”执行的操作。工厂函数所能做的就是返回一个引用。

        【讨论】:

          猜你喜欢
          • 2010-09-24
          • 1970-01-01
          • 2022-08-14
          • 1970-01-01
          • 1970-01-01
          • 2022-09-18
          • 2013-09-03
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多