重载构造函数/方法的最优雅方法是什么?

时间:2010-02-02 09:35:00

标签: c# overloading

重载构造函数和方法似乎凌乱,即简单地按参数的顺序和数量区分它们。是不是有办法,也许用泛型来干净利落地这样做,即使你只有一个参数(例如字符串idCode /字符串状态),你仍然可以区别他们?

using System;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            TheForm tf1 = new TheForm("online", DateTime.Now);
            TheForm tf2 = new TheForm(DateTime.Now, "form1");
        }
    }

    public class TheForm
    {
        public TheForm(string status, DateTime startTime)
        {
           //...
        }

        public TheForm(DateTime startTime, string idCode)
        {
           //...
        }
    }
}

14 个答案:

答案 0 :(得分:13)

如果您需要那么多重载,也许您的类型处理太多(请参阅Single Responsibility Principle)。就个人而言,我很少需要一个或几个构造函数。

答案 1 :(得分:8)

您可以考虑为该课程设置 Fluent Builder ,尽管这样做更多。这将允许你写这样的东西:

var form = new TheFormBuilder().WithStatus("foo").WithStartTime(dt).Build();

它更明确,但没有必要更好。这绝对是更多的工作。

在C#4中,您可以选择在调用构造函数时编写参数名称:

var form = new TheForm(status: "Foo", startTime: dt);

答案 2 :(得分:6)

.NET 3.0的新对象初始化功能比重载的构造函数更灵活。这是一个简单的例子:

public class Item
{
    public string Name {get; set;}
    public int Index {get; set;}
    public string Caption {get; set;}
} 

正如现在所写,我们可以在代码中执行以下操作:

var x = new item {Name=”FooBar”};
var x = new item {Name=”FooBar”, Index=”1”, Caption=”Foo Bar”};

如果我想在属性初始化期间添加功能,我只会向类Item添加重载的构造函数。例如:

public class Item
{
    public Item() {}

    public Item(string name)
    {
        Name = name;
        Caption = name; //defaulting name to caption
    }

    public Item(string name, int index) : this(name)
    {
        Index = index;
    }

    public Item(string name, int index, string caption) : this(name, int)
    {
        Caption = caption;
    }

    public string Name {get; set;}
    public int Index {get; set;}
    public string Caption {get; set;}
} 

注意:如果这是一个子类,我可以使用“base”关键字链接到父构造函数。

如果我正在编写“配置”类的类,我使用Fluent Methods代替重载的构造函数。

例如,如果我将这些方法添加到Item类:

public Item WithName(string name)
{
    Name = name;
    return this;
}
public Item WithIndex(int index)
{
    Index = index;
    return this;
}
public Item WithCaption(string caption)
{
    Caption = caption;
    return this;
}

我可以写这样的代码:

var x = new Item().WithName(“FooBar”).WithIndex(“99”).WithCaption(“Foo Bar”); 

答案 3 :(得分:5)

我能想到的将构造与给定类型的单个参数区分开来的唯一方法是在类型本身或工厂类中使用非实例工厂方法。

e.g。 (关于类型本身)

(未测试的)

public class TheForm 
{ 
    public static TheForm CreateWithId(string idCode)
    {
    }

    public static TheForm CreateWithStatus(string status)
    {
    }
} 

答案 4 :(得分:2)

在Fluent构建器之前,我们有时会设法使用参数对象或设置对象:

public class FormSetup {
  public string Status ...
  public string Id ...
}


var frm = new MyForm(new FormSetup { Status = "Bla", ... });

答案 5 :(得分:1)

构造函数转发!

答案 6 :(得分:1)

使用帮助器初始化类来传递重载的语义。

因此,例如,定义

public class TheForm
{
    public class TheForm(ById initializer)
    {
        //...
    }

    public class TheForm(ByStatus initializer)
    {
        //...
    }

    // ... 

    public class ById
    {
        public ById(DateTime startTime, string idCode)
        // ...
    }

    public class ByStatus
    {
        public ByStatus(string status, DateTime startTime)
        // ...
    }
}

但是,如果可以的话,更喜欢使用更常用的类,而不仅仅是用于初始化。您可能希望以不同的方式考虑您的课程。我感觉到代码味道的可能性:你的TheForm类是否包含太多的业务逻辑?例如,您是否想要拆分MVC控制器?

答案 7 :(得分:1)

在C#中(与许多其他编程语言一样)在这种情况下,您应该使用Factory Methods。像这样:

class TheForm
{
  public static TheForm CreateFromId(string idCode);
  public static TheForm CreateFromStatus(string status);
}

或小说参数:

class TheForm
{
  public TheForm(string idCode, int);
  public TheForm(string status);
}

或者你可以使用Eiffel;):

class THE_FORM create
   make_from_id, make_from_status
feature
  ...
end

答案 8 :(得分:1)

我们使用属性而不是重载构造函数,它非常干净且易于实现:

public class x {
  public string prop1 {get;set;}
  public DateTime prop2 {get;set;}
  ...
}

然后在实例化时(和/或更晚)填充您需要的属性

var obj = new x() {
  prop1 = "abc",
  prop2 = 123
};

这样做的好处是可以与.Net 3.5配合使用,并且可以清楚地了解所设置的内容。 (而不是var obj = new x("abc", 123, true, false, ... etc),你必须猜测每个值的含义,当有很多重载时,它会变得很毛茸茸)

答案 9 :(得分:1)

以下是一个例子:

Timespan.FromMilliseconds(double)
Timespan.FromSeconds(double)
Timespan.FromMinutes(double)
Timespan.FromHours(double)
Timespan.FromDays(double)

答案 10 :(得分:0)

这不是遗产的来源吗? 只需将TheForm作为基类,然后使用TheFormWithID和TheFormWithStatus子类。 让它们的构造函数将字符串ID和字符串Status分别传递回DateTime值。

我没有任何编码工具,所以请原谅语法。我相信你会弄清楚的。

using System;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            TheForm tf1 = new TheFormWithStatus(DateTime.Now, "online");
            TheForm tf2 = new TheFormWithID(DateTime.Now, "form1");
        }
    }

    public class TheForm
    {
        public TheForm(DateTime startTime)
        {
           //...
        }

    }

    public class TheFormWithID : TheForm
    {
        public TheFormWithID (DateTime startTime, string idCode) : TheForm (startTime)
        {
           //...
        }
    }

    public class TheFormWithStatus : TheForm
    {
        public TheFormWithStatus (DateTime startTime, string status) : TheForm (startTime)
        {
           //...
        }
    }
}

或将TheForm作为抽象类。

答案 11 :(得分:0)

我没有得到你在多个构造函数中找到的“混乱”。我觉得返回对象实例的静态方法也是一种可能的替代方法。

但是,如果有人想要使用单个构造函数并且仍然有不同的实现,我们可以考虑将从某个接口派生的对象作为构造函数的输入传递,并且可能会检查输入的类型以创建实例。在这种情况下,这是一种抽象工厂。

在一个地方,我们有一个类如下:

using System;

namespace MyApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            base1 t1 = new type1();
            class1 c1 = new class1(t1);
            base1 t2 = new type2();
            class1 c2 = new class1(t2);
            //.....
        }
    }

    public class class1
    {
        public class1(base1 mytype)
        {
           switch(mytype.type)
               case mytype.types.type1
                   return createObjectOftype1();
               case mytype.types.type2
                   return createObjectOftype2();
               case mytype.types.type3
                   return createObjectOftype3();
        }

        public class1 createObjectOftype1()
        {
            //....
        }

        public class1 createObjectOftype2()
        {
           //...
        }

        public class1 createObjectOftype2()
        {
           //...
        }

    }


    public class base1
    {
        publlic Enum Types {0 "type1",.... 
    }

    public class type1:base1
    {
        //.....
    }

    public class type2:base1
    {
        //.....
    }
}

答案 12 :(得分:0)

无论你是否在谈论构造函数,重载都非常有限,当你开始遇到它的极限时,这暗示它不适合这项工作。

值得一看的是设计良好的API,它使用重载来了解该工具适合哪种工作。 XmlReader.Create是一个很好的例子:它支持12种不同的重载。十二!然而,它实际上是完全合情合理的:当你仔细研究它们时,它们归结为Python中的单个调用签名,带有可选参数:

XmlReader.Create(input [, settings [, parser_context]])
对于此方法,

input可以是包含网址或文件名,TextReaderStream的字符串。但无论其数据类型如何,它仍然基本上是相同的:XmlReader将要读取的数据来源。

现在让我们来看看你的情况。暂时忘掉数据类型。在您的应用程序中,statusidCode之间显然存在一些功能差异。如果给定status,则表单将采用单向方式,如果给出idCode,则表单采用另一种方式。您提议的API 隐藏这种功能差异。它应该照亮它。

我首先考虑最简单的方法,它根本不使用任何重载:

TheForm(string idCode, string status)

如果提供了两个值(或两者都为null),则使构造函数抛出异常。请注意,它们在文档中是互斥的。叫它一天。

我的第二选择是:

enum FormType
{
   IdCode,
   Status
};

TheForm(FormType type, string data)

这不太简洁,但它具有非常大的优点,使得该方法支持显式的多个互斥模式。

我称之为enum FormType因为它看起来像一个明智的名字,鉴于我目前所知,以及这个方法是构造函数的事实。但是,无论何时考虑创建枚举以确定实例的类型,您至少应该考虑是否应该创建类型来确定实例的类型:

class TheFormWhatUsesIdCode : TheForm {...}
class TheFormWhatUsesStatus : TheForm {...}

idCodestatus之间的功能差异可能与使用idCode实例化的表单和使用status实例化的表单之间的功能差异有关。这强烈暗示它们应该是子类。

在所有这些分析中,我从未考虑过做你实际要求的可能性,即提供多次重载。我不认为重载是这项工作的正确工具。如果idCodeintstatusstring仍然不会认为重载是这项工作的正确工具,尽管在我需要重构很多代码之前,我可能不会注意到它。

答案 13 :(得分:0)

我个人不喜欢其他类能够设置我的属性的想法

因此,这可以保护或保护我的财产,但仍然具有其他答案所述的许多功能:

public class FooSettings
{
    public bool Prop1 { get; set; }
    public bool Prop2 { get; set; }

    public TimeSpan Prop3 { get; set; }

    public FooSettings()
    {
        this.Prop1 = false;
        this.Prop2 = false;

        this.Prop3 = new TimeSpan().ExtensionMethod(CustomEnum.Never);
    }

    public FooSettings BoolSettings
    (bool incomingFileCacheSetting, bool incomingRuntimeCacheSetting)
    {
        this.Prop1 = incomingFileCacheSetting;
        this.Prop2 = incomingRuntimeCacheSetting;
        return this;
    }

    public FooSettings Prop3Setting
    (TimeSpan incomingCustomInterval)
    {
        this.Prop3 = incomingCustomInterval;
        return this;
    }

    public FooSettings Prop3Setting
    (CustomEnum incomingPresetInterval)
    {
        return this.Prop3Setting(new TimeSpan().ExtensionMethod(CustomEnum.incomingPresetInterval));
    }
}


public class Foo
{
    public bool Prop1 { get; private set; }
    public bool Prop2 { get; private set; }

    public TimeSpan Prop3 { get; private set; }

    public CallTracker
    (
        FooSettings incomingSettings
    )
    {
        // implement conditional logic that handles incomingSettings
    }
}

然后可以用作:

FooSettings newFooSettings = new FooSettings {Prop1 = false, Prop2 = true}
newFooSettings.Prop3Setting(new TimeSpan(3,0,0));

Foo newFoo = new Foo(newFooSettings)

FooSettings newFooSettings = new FooSettings()
    .BoolSettings(false, true)
    .Prop3Setting(CustomEnum.Never)

Foo newFoo = new Foo(newFooSettings)

对于一个简单的类来说显然有点过大了,但是它对可以归纳为单个属性IE的数据类型提供了很多控制:可以使用扩展方法从自定义枚举类型解析TimeSpan