C# 选择正确的构造函数

C# 选择正确的构造函数,c#,constructor,C#,Constructor,我有两个类:Purchase(父类)和DiscountPurchase(子类,带有一个附加字段“折扣”)。每个都有2个构造函数(带参数和不带参数)。我需要解析字符串并创建一个实例。我知道,我可以这样做: string[] parameters = csvString.Split(';'); string productName = parameters[0]; decimal cost = decimal.Parse(parameters[1]); int productCount = int

我有两个类:
Purchase
(父类)和
DiscountPurchase
(子类,带有一个附加字段“折扣”)。每个都有2个构造函数(带参数和不带参数)。我需要解析字符串并创建一个实例。我知道,我可以这样做:

string[] parameters = csvString.Split(';');

string productName = parameters[0];
decimal cost = decimal.Parse(parameters[1]);
int productCount = int.Parse(parameters[2]);

if (parameters.Length < 4)
{                   
    newPurchase = new Purchase(productName, cost, productCount);
}
else
{
    decimal discount = decimal.Parse(parameters[3]);
    newPurchase = new FixedDiscountPurchase(productName, cost, productCount, discount);
}
string[]parameters=csvString.Split(“;”);
字符串productName=参数[0];
十进制成本=decimal.Parse(参数[1]);
int productCount=int.Parse(参数[2]);
如果(参数长度<4)
{                   
newPurchase=新购买(产品名称、成本、产品计数);
}
其他的
{
十进制折扣=decimal.Parse(参数[3]);
newPurchase=new FixedDiscountPurchase(产品名称、成本、产品计数、折扣);
}

但也许有更优雅的方式:反射或其他什么?

最好重新设计您的
Purchase
类,使其具有
折扣
字段,然后执行如下操作:

string[] parameters = csvString.Split(';');

string productName = parameters[0];
decimal cost = decimal.Parse(parameters[1]);
int productCount = int.Parse(parameters[2]);
decimal discount = parameters.Length < 4 ? 0 : decimal.Parse(parameters[3]);

newPurchase = new Purchase(productName, cost, productCount, discount);
static class PurchaseFactory
{
    public Static Purchase BuildPurchase(String[] parameters){
       string productName = parameters[0];
       decimal cost = decimal.Parse(parameters[1]);
       int productCount = int.Parse(parameters[2]);

       if (parameters.Length < 4)
       {                   
           return new Purchase(productName, cost, productCount);
       }
       else
       {
           decimal discount = decimal.Parse(parameters[3]);
           return new FixedDiscountPurchase(productName, cost, productCount, discount);
       }
    }
}

如果需要在逻辑上区分未指定折扣和指定折扣0之间的差异,则可以对
折扣使用and
null
(而不是
0
)。

这是工厂模式的典型用例。您知道,您需要一个
Purchase
实例,但您不知道确切的子类型。(请参阅和此处了解更复杂、更有用的示例:)

Ofc。这并没有避免您已经实现的逻辑-它只是帮助您不必反复重复自己,并且将逻辑封装在一个工厂中,您可以随时在任何地方使用

根据您的示例,一个简单的工厂可能如下所示:

string[] parameters = csvString.Split(';');

string productName = parameters[0];
decimal cost = decimal.Parse(parameters[1]);
int productCount = int.Parse(parameters[2]);
decimal discount = parameters.Length < 4 ? 0 : decimal.Parse(parameters[3]);

newPurchase = new Purchase(productName, cost, productCount, discount);
static class PurchaseFactory
{
    public Static Purchase BuildPurchase(String[] parameters){
       string productName = parameters[0];
       decimal cost = decimal.Parse(parameters[1]);
       int productCount = int.Parse(parameters[2]);

       if (parameters.Length < 4)
       {                   
           return new Purchase(productName, cost, productCount);
       }
       else
       {
           decimal discount = decimal.Parse(parameters[3]);
           return new FixedDiscountPurchase(productName, cost, productCount, discount);
       }
    }
}

好吧,如果只是这两个类,并且希望知道价格是否打折(并计算最终价格)-您可以使用一个
购买
类,该类包含打折标志和在任何情况下获取最终价格的方法:

public class Purchase
    {
        public Decimal Discount { get; set; }
        public Boolean Discounted { get; set; }
        public String Name { get; set; }
        public Decimal Price { get; set; }
        public Int32 Count { get; set; }

        public Decimal FinalPrice
        {
            get
            {
                if (!Discounted)
                    return Price;
                else
                    return Price - Discount;
            }
        }

        public Purchase (String csvString){
            string[] parameters = csvString.Split(';');

            Name = parameters[0];
            Price = decimal.Parse(parameters[1]);
            Count = int.Parse(parameters[2]);

            if (parameters.Length == 4)
            {
                Discount = decimal.Parse(parameters[3]);
                Discounted = true;
            }
        }
    }
用法:

Purchase p = new Purchase(stringInput);
MessageBox.Show(p.FinalPrice.ToString());

只需确保参考
购买。最终价格
,那么你就不必关心价格是否真的打折了。

我认为每个人都在考虑这个问题。如果这是您正在创建的一个全新项目,并且正在进行中,那么除了
购买
折扣购买
之外,还有其他类型的项目,那么dognose关于创建工厂的答案可能是可行的

不过,不必撕碎代码并做大量工作,扩展方法可能是最好的选择:

public static Purchase GetPurchaseObject(this string csvString)
{
    string[] parameters = csvString.Split(';');

    string productName = parameters[0];
    decimal cost = decimal.Parse(parameters[1]);
    int productCount = int.Parse(parameters[2]);

    if (parameters.Length < 4)
    {
        return new Purchase(productName, cost, productCount);
    }
    else
    {
        decimal discount = decimal.Parse(parameters[3]);
        return new DiscountPurchase(productName, cost, productCount, discount);
    }
}

从输出中可以看到,
p1
p2
是不同的对象!根据CSV字符串的内容,它将知道您需要哪种类型的对象。另外,如果您不喜欢扩展方法,可以将逻辑移到静态方法中,并将csv字符串作为参数传递给该方法。

下面是使用反射的方法。它通过尝试使用它看到的第一个具有正确数量的参数的构造函数来工作。它搜索的类型列表是您给它的类型加上与该类型在同一程序集中直接从该类型继承的任何类型

它非常脆弱,难以阅读,而且不灵活(例如,如果您在其构造函数中添加了另一种带有三个参数的购买类型,它将无法正常工作),这让您了解使用反射的感觉我不建议您这样做。

static object CreateInstance(Type rootType, object[] args)
{
    var types = rootType.Assembly.GetTypes().Where(t =>
        t == rootType || t.BaseType == rootType).ToArray();
    return CreateInstance(types, args);
}
static object CreateInstance(Type[] types, object[] args)
{
    foreach (var type in types)
    {
        foreach (var ctor in type.GetConstructors())
        {
            var parameters = ctor.GetParameters();
            if (args.Length == parameters.Length)
            {
                var newArgs = args.Select((x, i) =>
                   Convert.ChangeType(x, parameters[i].ParameterType)).ToArray();
                return ctor.Invoke(newArgs);
            }
        }
    }
    return null;
}

// use like
var newPurchase = CreateInstance(typeof(Purchase), parameters); // or
var newPurchase = CreateInstance(
         new[] { typeof(Purchase), typeof(FixedDiscountPurchase) }, parameters);

你们试过你们的反射想法吗?我不能得到类的构造函数,我不知道哪种类型。问题是如何找出调用哪一个类构造函数。谢谢大家的回答。这对我来说是非常有益的。现在,我认为最合理的方式是制造工厂。但是关于扩展方法的想法也很好,但是你所做的只是把他的90%的逻辑转移到另一种方法。这并没有改善代码的编写方式,只是将其重构到另一个地方(不可否认,这可能会使代码更干净一些)。@TimS。我同意您的观点(并且已经写到这并不能避免实现),但这只是因为示例非常简单。您拥有的类类型越多,工厂就越有意义。反射可能不起作用,因为他实际上不知道首先使用哪个类。@dognose出于好奇,你大部分时间都是
Java
dev吗?我这样问是因为
buildPurchase
camelCase方法:)@Michael-well-spotted:-)@Konstantin:对不起,这里没有魔法。在某些时候,您必须实现逻辑。工厂方法的美妙之处在于它将实现细节推到了边缘,这样您就不必在每次加载对象时都编写
if…else
。哇,它看起来真令人毛骨悚然。谢谢你的解释,现在对我来说似乎更清楚了。:)
static object CreateInstance(Type rootType, object[] args)
{
    var types = rootType.Assembly.GetTypes().Where(t =>
        t == rootType || t.BaseType == rootType).ToArray();
    return CreateInstance(types, args);
}
static object CreateInstance(Type[] types, object[] args)
{
    foreach (var type in types)
    {
        foreach (var ctor in type.GetConstructors())
        {
            var parameters = ctor.GetParameters();
            if (args.Length == parameters.Length)
            {
                var newArgs = args.Select((x, i) =>
                   Convert.ChangeType(x, parameters[i].ParameterType)).ToArray();
                return ctor.Invoke(newArgs);
            }
        }
    }
    return null;
}

// use like
var newPurchase = CreateInstance(typeof(Purchase), parameters); // or
var newPurchase = CreateInstance(
         new[] { typeof(Purchase), typeof(FixedDiscountPurchase) }, parameters);