需要所有参数和所有参数的类构造函数

时间:2016-04-22 23:01:39

标签: c# initialization

有没有办法定义我的类,以便该类的调用者获得编译时错误,除非他们指定了类的每个属性,并且参数传递的附加约束被命名?

我们说我有这个界面:

public interface IPerson
{
    string FirstName {get;}
    string MiddleName { get; }
    string LastName { get;}
}

这些课程:

public class PersonNeedAllProperties : IPerson
{
    public string FirstName { get; private set;} // ideally readonly
    public string MiddleName { get; private set;}
    public string LastName { get; private set;}

    public PersonNeedAllProperties(
        string firstName,
        string lastName,
        string middleName)
    {
        this.FirstName = firstName;
        this.MiddleName = middleName;
        this.LastName = lastName;
    }
}

public class PersonCanNamePropertiesButNotSadlyNotRequired : IPerson
{
    public string FirstName { get; set;} // ideally readonly
    public string MiddleName { get; set;}
    public string LastName { get; set;}
}

然后这些imho的问题如下:

var p1 = new PersonNeedAllProperties("lastName", "firstName", "middle");
 // woops wrong order because not named.

var p2 = new PersonCanNamePropertiesButNotSadlyNotRequired()
{
    FirstName = "firstName",
    LastName = "lastName"
}; // woops forgot all about the middlename.

有没有办法让一个简单的POCO对象具有类似于第二人的初始化,但这要求所有属性被命名?

以上是上述的小提琴:https://dotnetfiddle.net/gUk8eT#

4 个答案:

答案 0 :(得分:2)

回答你的问题不,这是不可能的。

你要求能够强制做两件事:

  • a)强制设置所有属性
  • b)强制命名属性。

这些事情可以单独完成,但不能合并,因为这样做的方法截然相反,如下所述。

a)要强制设置所有属性,您需要将它们作为构造函数中的输入。

b)要强制按名称设置属性,不能在构造函数中指定它们。

附注:可以为构造函数提供命名参数,但不可能强制这些参数var person = new Person(firstName: 'John', lastName: 'Doe', middleName: 'A')

您可以获得的最接近的是标记您的属性readonly(私有集),并且只能在您的PersonNeedAllProperties类中的构造函数中设置它们。

可能是可行替代方案的东西称为Builder Pattern。 这将有一个额外的类负责构造您的对象,但您仍然无法获得编译时错误,只有运行时。

答案 1 :(得分:2)

几乎强制执行此操作的可怕方式。不是 name 而是 type 。我不推荐它。但有一种可怕的方法可以做到这一点。我提到它太可怕了吗?

  

"计算机科学中的任何问题都可以通过添加另一层间接来解决。 - 惠勒?

将每个属性设为自己的 distinct type 。现在你不能以错误的顺序传递它们,否则编译器会告诉你传递了无效的类型。

public class FirstName
{
    string Value { get; set; }
}

public class MiddleName
{
    string Value { get; set; }
}

public class LastName
{
    string Value { get; set; }
}

public interface IPerson
{
    FirstName FirstName {get;}
    MiddleName MiddleName { get; }
    LastName LastName { get;}
}

public class PersonNeedAllProperties : IPerson
{
    public FirstName FirstName { get; private set;} // ideally readonly
    public MiddleName MiddleName { get; private set;}
    public LastName LastName { get; private set;}

    public PersonNeedAllProperties(
        FirstName firstName,
        MiddleName lastName,
        LastName middleName)
    {
        this.FirstName = firstName;
        this.MiddleName = middleName;
        this.LastName = lastName;
    }
}

答案 2 :(得分:2)

这可以通过嵌套类来实现:

public interface IPerson
{
    string FirstName { get; }
    string MiddleName { get; }
    string LastName { get; }
}

public class Person : IPerson
{
    public string FirstName { get; private set; }
    public string MiddleName { get; private set; }
    public string LastName { get; private set; }

    //Make sure the parameterless constructor is private
    private Person() { }

    private Person(string first, string middle, string last)
    {
        this.FirstName = first;
        this.MiddleName = middle;
        this.LastName = last;
    }

    public class Builder
    {
        private Person person = new Person();

        public Builder WithFirstName(string first)
        {
            person.FirstName = first;
            return this;
        }

        public Builder WithMiddleName(string middle)
        {
            person.MiddleName = middle;
            return this;
        }

        public Builder WithLastName(string last)
        {
            person.LastName = last;
            return this;
        }

        public IPerson Build()
        {
            if (person.FirstName != null
                && person.MiddleName != null
                && person.LastName != null)

                return person;

            throw new Exception("Cannot build person because reasons...");
        }
    }
}

然后按如下方式使用:

var person = new Person.Builder()
                    .WithFirstName("Rob")
                    .WithMiddleName("<I have no middle name>")
                    .WithLastName("Janssen")
                    .Build();

您可以按照自己喜欢的顺序使用With...方法:

var person = new Person.Builder()
                    .WithLastName("Janssen")
                    .WithFirstName("Rob")
                    .WithMiddleName("<I have no middle name>")
                    .Build();

Fiddle here

编辑:废话;我没有注意到编译时错误的其他要求。

然而,这会“强制”您设置所有3个必填字段,并“强制”您使用With...方法“命名”字段,这使得很难混淆值并且还允许您按所需顺序指定值。它还会阻止您自己实例化Person并允许您拥有私有设置器(例如“只读”属性)并保持接口完好无损。这里缺少唯一的东西是编译时错误;你不会在这里得到一个。也许Code Contracts可以帮助该部门,但至少需要一些“团队协调”(例如,每个人都需要install an extension并拥有static checking enabled);如果可以通过代码合同完成,我也不能100%确定。

答案 3 :(得分:0)

不,接口的设计不符合这种方式。

接口确保实现它的类将具有在接口中声明的所有元素。但是没有就如何实施它们发表任何声明。

您可以使用自定义构造函数的抽象类来完成您想要的任务。

public abstract class Person
{
    public abstract string FirstName {get; protected set; }
    public abstract string MiddleName { get; protected set; }
    public abstract string LastName { get; protected set; }

    public Person(string firstName, string middleName, string lastName){
        FirstName = firstName;
        MiddleName = middleName;
        LastName = lastName;
    }
}

public class PersonNeedAllProperties : Person
{
    public override string FirstName{get; protected set;}
    public override string MiddleName{get; protected set;}
    public override string LastName{get; protected set;}

    public PersonNeedAllProperties(
        string firstName,
        string lastName,
        string middleName)
        : base(firstName, lastName, middleName)
    {
    }
}

自定义构造函数强制您完成子类中的名称。

您可以将此答案与John Gardner之一结合使用类而不是字符串作为名称(firstName,middleName,lastName)来模拟命名参数。

您需要了解此方法的局限性,在C#中,您无法从多个类继承,因此如果您决定实施此解决方案,则需要牢记此限制。

相关问题