2016-04-22 97 views
1

是否有方法来定义我的类,以便该类的调用者将获得编译时错误,除非他们指定了该类的每个属性,并且附加的约束条件是该参数通过命名?要求所有参数和所有参数命名的类构造函数

比方说,我有这样的接口:

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;} 
} 

然后用这些恕我直言的问题如下:

​​

是不是有一个获得一个简单的POCO对象,其初始化类似于人2,但这需要全部属性被命名?

这里是上面的小提琴:https://dotnetfiddle.net/gUk8eT#

+0

在指定构造函数时,不能让编译器强制类的使用者命名参数。你想这么做吗? –

+0

@EricJ:目标是避免场景1的场景。有时我们有多个字符串,混淆顺序非常简单。我的目标是尽可能让同伴创造一个bug。 – user420667

+0

所以让所有的开发者拼出名字。好主意,因为他们喜欢额外的按键。 – Paparazzi

回答

2

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

你问到能够迫使两件事情:

  • 一)强制所有属性中设置
  • 二)力属性命名。

这些东西可以独立完成,但它们不能组合,因为这样做的方法是完全相反的,如下所述。

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

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

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

你可以得到的最接近的是只读(私人设置)标志着你的属性,只有让他们可以在您的构造函数中设置,就像您在PersonNeedAllProperties课程中所做的一样。

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

+0

+1可能是正确的。然而,这是不幸的。任何方式来证明这是不可能的?谢谢。 – user420667

+1

@ user420667我已经添加了一些额外的信息,可以帮助您更接近您正在寻找的内容。 – Phaeze

2

有一个可怕的方式几乎强制执行此。不通过名称而是通过类型。我不推荐它。但有一个可怕的方式来做到这一点。我提到它是可怕的吗?

“计算机科学中的任何问题都可以通过添加另一层间接寻址来解决” - Wheeler?

使每个属性都属于自己的不同类型。现在你不能以错误的顺序传递它们,否则编译器会告诉你,你传递了一个无效类型。

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; 
    } 
} 
+0

它并没有给你所需的一切,但它确实强制人们按顺序通过特定的参数。你可能会完全忽略这个接口,如果你想强制它们是非空的,你只需要一个具体的类。 –

+0

对于我们的一些场景,这实际上是一个不错的主意。很多时候,这不是它的命名,它应该被解释(和验证)为其他事物的字符串,例如, URI或英文名称。其他时候,虽然它只是普通的布尔值,但看到一串True/False对于查看/调试并不有趣。引起布尔问题的一个提议是Enums。 – user420667

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#中,您不能从多个类继承,因此如果您决定实施此解决方案,则需要牢记此限制。

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%确定它是否可以通过代码合同完成。

+0

OP的问题有趣的解决方案。 –

+0

虽然不是我想到的,但感谢您提供了构建器模式的示例以及如此流畅的内容。有趣。 – user420667

相关问题