如何重构所有C#Switch语句之母

如何重构所有C#Switch语句之母,c#,refactoring,C#,Refactoring,我正在尝试重构所有交换机之母,但不确定如何进行重构。以下是现行守则: bool FieldSave(Claim claim, string field, string value) { //out vars for tryparses decimal outDec; int outInt; bool outBool; DateTime outDT; //our return valu

我正在尝试重构所有交换机之母,但不确定如何进行重构。以下是现行守则:

    bool FieldSave(Claim claim, string field, string value)
    {
        //out vars for tryparses
        decimal outDec;
        int outInt;
        bool outBool;
        DateTime outDT;

        //our return value
        bool FieldWasSaved = true;

        //World Greatest Switch - God help us all.
        switch (field)
        {
            case "Loan.FhaCaseNumber":
                GetLoan(claim).FhaCaseNumber = value;
                break;
            case "Loan.FhaInsurance":
                if (bool.TryParse(value, out outBool))
                {
                    GetLoan(claim).FhaInsurance = outBool;
                    FieldWasSaved = true;
                }
                break;
            case "Loan.UnpaidPrincipalBalance":
                if (decimal.TryParse(value, out outDec))
                {
                    GetLoan(claim).UnpaidPrincipalBalance = outDec;
                    FieldWasSaved = true;
                }
                break;
            case "Loan.Mortgagor_MortgagorID":
                if(Int32.TryParse(value, out outInt)){
                    GetLoan(claim).Mortgagor_MortgagorID = outInt;
                    FieldWasSaved = true;                        
                }
                break;
            case "Loan.SystemDefaultDate":
                if (DateTime.TryParse(value, out outDT))
                {
                    GetLoan(claim).SystemDefaultDate = outDT;
                    FieldWasSaved = true;
                }                    
                break;
            //And so on for 5 billion more cases
        }

        db.SaveChanges();
        return FieldWasSaved;  
    }   
到底有没有通用的方法来实现这一点,或者这个超级开关确实是必要的

多一点语境
我并不认为我能理解其他开发人员的神奇之处,但基本上字符串“Loan.FieldName”来自于HTML输入标记上的一些元数据。此开关中使用此选项将特定字段链接到实体框架数据表/属性组合。尽管这来自强类型视图,但出于超出人类知识范围的原因,此映射已成为将整个内容粘合在一起的粘合剂。

如果case语句中的名称与类中的属性匹配,我会将其全部更改为使用反射

例如,这里是我们基本业务记录核心的精简版本,我们使用它将数据移入和移出数据库、表单、web服务等

    public static void SetFieldValue(object oRecord, string sName, object oValue)
    {
        PropertyInfo theProperty = null;
        FieldInfo theField = null;
        System.Type oType = null;

        try
        {
            oType = oRecord.GetType();

            // See if the column is a property in the record
            theProperty = oType.GetProperty(sName, BindingFlags.Instance | BindingFlags.IgnoreCase | BindingFlags.Public, null, null, new Type[0], null);
            if (theProperty == null)
            {
                theField = oType.GetField(sName, BindingFlags.Instance | BindingFlags.IgnoreCase | BindingFlags.Public);
                if (theField != null)
                {
                    theField.SetValue(oRecord, Global.ValueFromDB(oValue, theField.FieldType.Name));
                }
            }
            else
            {
                if (theProperty.CanWrite)
                {
                    theProperty.SetValue(oRecord, Global.ValueFromDB(oValue, theProperty.PropertyType.Name), null);
                }
            }
        }
        catch (Exception theException)
        {
            // Do something useful here
        }
   }
在上面的例子中,Global.ValueFromDB是一个大的switch语句,可以安全地将值转换为指定的类型。以下是部分版本:

    public static object ValueFromDB(object oValue, string sTypeName)
    {
        switch (sTypeName.ToLower())
        {
            case "string":
            case "system.string":
                return StrFromDB(oValue);

            case "boolean":
            case "system.boolean":
                return BoolFromDB(oValue);

            case "int16":
            case "system.int16":
                return IntFromDB(oValue);

            case "int32":
            case "system.int32":
                return IntFromDB(oValue);
其中特定于fromdb的数据类型如下所示:

    public static string StrFromDB(object theValue)
    {
        return StrFromDB(theValue, "");
    }
    public static string StrFromDB(object theValue, string sDefaultValue)
    {
        if ((theValue != DBNull.Value) && (theValue != null))
        {
            return theValue.ToString();
        }
        else
        {
            return sDefaultValue;
        }
    }
    public static bool BoolFromDB(object theValue)
    {
        return BoolFromDB(theValue, false);
    }
    public static bool BoolFromDB(object theValue, bool fDefaultValue)
    {
        if (!(string.IsNullOrEmpty(StrFromDB(theValue))))
        {
            return Convert.ToBoolean(theValue);
        }
        else
        {
            return fDefaultValue;
        }
    }
    public static int IntFromDB(object theValue)
    {
        return IntFromDB(theValue, 0);
    }
    public static int IntFromDB(object theValue, int wDefaultValue)
    {
        if ((theValue != DBNull.Value) && (theValue != null) && IsNumeric(theValue))
        {
            return Convert.ToInt32(theValue);
        }
        else
        {
            return wDefaultValue;
        }
    }
delegate bool FieldSaveDelegate(Claim claim, string value);

在短期内,您可能不会节省太多代码,但一旦它被实现,您会发现它有很多很多用途(我们当然有)。

通常在我重构时,它是为了以某种方式降低代码的复杂性,或者使代码更容易理解。在你发布的代码中,我不得不说它看起来并没有那么复杂(虽然可能有很多行,但看起来非常重复和直接)。因此,除了代码美学之外,我不确定重构一个开关会带来多少好处

话虽如此,我可能会创建一个字典,其中它们的键是
字段
,值是一个委托,其中包含每种情况下的代码(每个方法可能会返回一个bool,其中包含
字段已保存
值,并且对于这4个其他值会有一些out参数)。然后,您的方法将只使用字段从字典中查找委托,然后调用它

当然,我可能会让代码保持原样。字典方法对其他开发人员来说可能不那么明显,可能会使代码不那么容易理解


更新:我也同意nightwatch的观点,最好的重构可能会涉及到未显示的代码——可能很多代码属于其他类(可能会有一个Loan类封装所有Loan字段,或者类似的东西……).

一种可能是创建一个
字典,其中字段名作为键,委托作为值。比如:

    public static string StrFromDB(object theValue)
    {
        return StrFromDB(theValue, "");
    }
    public static string StrFromDB(object theValue, string sDefaultValue)
    {
        if ((theValue != DBNull.Value) && (theValue != null))
        {
            return theValue.ToString();
        }
        else
        {
            return sDefaultValue;
        }
    }
    public static bool BoolFromDB(object theValue)
    {
        return BoolFromDB(theValue, false);
    }
    public static bool BoolFromDB(object theValue, bool fDefaultValue)
    {
        if (!(string.IsNullOrEmpty(StrFromDB(theValue))))
        {
            return Convert.ToBoolean(theValue);
        }
        else
        {
            return fDefaultValue;
        }
    }
    public static int IntFromDB(object theValue)
    {
        return IntFromDB(theValue, 0);
    }
    public static int IntFromDB(object theValue, int wDefaultValue)
    {
        if ((theValue != DBNull.Value) && (theValue != null) && IsNumeric(theValue))
        {
            return Convert.ToInt32(theValue);
        }
        else
        {
            return wDefaultValue;
        }
    }
delegate bool FieldSaveDelegate(Claim claim, string value);
然后,可以为每个字段编写单独的方法:

bool SystemDefaultDateHandler(Claim cliaim, string value)
{
    // do stuff here
}
并对其进行初始化:

FieldSaveDispatchTable = new Dictionary<string, FieldSaveDelegate>()
{
    { "Loan.SystemDefaultDate", SystemDefaultDateHandler },
    // etc, for five billion more fields
}
这可能比switch语句更易于维护,但它仍然不是特别漂亮。好在可以自动生成填充字典的代码

正如前面的回答所说,您可以使用反射来查找字段名以确保其有效,并检查类型(也可以使用反射)。这将使您编写的处理程序方法的数量减少到很少:每种类型一个

即使不使用反射,也可以通过在字典中保存类型而不是委托来减少所需的方法数。然后进行查找以获取字段的类型,并在类型上使用一个小的switch语句


当然,这是假设您不做任何特殊的字段处理。如果必须对某些字段执行特殊验证或附加处理,则每个字段需要一个方法,或者每个字段需要一些附加信息。

根据您的应用程序,您可能能够重新定义声明为动态对象:

class Claim : DynamicObject 
{
    ... // Current definition

    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        try
        {
            var property = typeof(Loan).GetProperty(binder.Name);
            object param = null;

            // Find some way to parse the string into a value of appropriate type:
            // (This will of course need to be improved to handle more types)
            if (property.PropertyType == typeof(Int32) )
            {
                param = Int32.Parse(value.ToString());
            }

            // Set property in the corresponding Loan object
            property.SetValue(GetLoan(this), param, null);
            db.Save();
        }
        catch
        {
            return base.TrySetMember(binder, value);
        }

        return true;
    }
}
如果可能,您应该能够使用以下语法通过索赔对象间接处理相应的贷款对象:


对于失败的情况,当输入字符串不能被解析时,很不幸地得到一个RunTimeBundRebug,而不是一个好的函数返回值,所以你需要考虑这是否适合你的情况。

我知道回答你自己的问题是个大问题,但我的老板是如何用反思和字典解决这个问题的。具有讽刺意味的是,在我们完成“所有开关之母”的几分钟后,他就完成了他的解决方案。没有人希望看到一个下午的打字变得毫无意义,但这个解决方案要灵活得多

    public JsonResult SaveField(int claimId, string field, string value)
    {
        try
        {
            var claim = db.Claims.Where(c => c.ClaimID == claimId).SingleOrDefault();
            if (claim != null)
            {
                if(FieldSave(claim, field, value))
                    return Json(new DataProcessingResult { Success = true, Message = "" });
                else
                    return Json(new DataProcessingResult { Success = false, Message = "Save Failed - Could not parse " + field });
            }
            else
                return Json(new DataProcessingResult { Success = false, Message = "Claim not found" });
        }
        catch (Exception e)
        {
            //TODO Make this better
            return Json(new DataProcessingResult { Success = false, Message = "Save Failed" });
        }
    }

    bool FieldSave(Claim claim, string field, string value)
    {

        //our return value
        bool FieldWasSaved = true;

        string[] path = field.Split('.');

        var subObject = GetMethods[path[0]](this, claim);

        var secondParams = path[1];
        PropertyInfo propertyInfo = subObject.GetType().GetProperty(secondParams);

        if (propertyInfo.PropertyType.IsGenericType && propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
        {
            FieldWasSaved = SetValue[Nullable.GetUnderlyingType(propertyInfo.PropertyType)](propertyInfo, subObject, value);
        }
        else
        {
            FieldWasSaved = SetValue[propertyInfo.PropertyType](propertyInfo, subObject, value);
        }


        db.SaveChanges();
        return FieldWasSaved;
    }

    // these are used for dynamically setting the value of the field passed in to save field
    // Add the object look up function here. 
    static Dictionary<string, Func<dynamic, dynamic, dynamic>> GetMethods = new Dictionary<string, Func<dynamic, dynamic, dynamic>>() 
    { 
        { "Loan", new Func<dynamic, dynamic, dynamic>((x, z)=> x.GetLoan(z)) },
        // and so on for the 15 or 20 model classes we have
    };

    // This converts the string value comming to the correct data type and 
    // saves the value in the object
    public delegate bool ConvertString(PropertyInfo prop, dynamic dynObj, string val);
    static Dictionary<Type, ConvertString> SetValue = new Dictionary<Type, ConvertString>()
    {
        { typeof(String), delegate(PropertyInfo prop, dynamic dynObj, string val)
            {
                if(prop.PropertyType == typeof(string))
                {
                    prop.SetValue(dynObj, val, null);
                    return true;
                }
                return false;
            }
        },
        { typeof(Boolean), delegate(PropertyInfo prop, dynamic dynObj, string val)
            {
                bool outBool = false;
                if (Boolean.TryParse(val, out outBool))
                {
                    prop.SetValue(dynObj, outBool, null);
                    return outBool;
                }
                return false;
            } 
        },
        { typeof(decimal), delegate(PropertyInfo prop, dynamic dynObj, string val)
            {
                decimal outVal;
                if (decimal.TryParse(val, out outVal))
                {
                    prop.SetValue(dynObj, outVal, null);
                    return true;
                }
                return false;
            } 
        },
        { typeof(DateTime), delegate(PropertyInfo prop, dynamic dynObj, string val)
            {
                DateTime outVal;
                if (DateTime.TryParse(val, out outVal))
                {
                    prop.SetValue(dynObj, outVal, null);
                    return true;
                }
                return false;
            } 
        },
    };
公共JsonResult保存字段(int-claimId、字符串字段、字符串值) { 尝试 { var claim=db.Claims.Where(c=>c.ClaimID==ClaimID.SingleOrDefault(); 如果(索赔!=null) { if(字段保存(索赔、字段、值)) 返回Json(新的DataProcessInResult{Success=true,Message=”“}); 其他的 返回Json(新的DataProcessInResult{Success=false,Message=“保存失败-无法解析”+字段}); } 其他的 返回Json(新数据处理结果{Success=false,Message=“Claim not found”}); } 捕获(例外e) { //要做得更好吗 返回Json(新数据处理结果{Success=false,Message=“Save Failed”}); } } bool FieldSave(索赔、字符串字段、字符串值) { //我们的回报价值 bool fieldwasaved=true; string[]path=field.Split('.'); var subObject=GetMethods[path[0]](此,声明); var secondParams=路径[1]; PropertyInfo PropertyInfo=subObject.GetType().GetProperty(secondParams); if(propertyInfo.PropertyType.IsGenericType&&propertyInf