在没有getter和setter方法的情况下为基类访问子类变量

时间:2012-10-02 21:59:54

标签: c# inheritance

基类MasterClass包含一个包含string键和HookObj值的Dictionary,其中HookObj保存(来自派生类)变量Type和引用它是get / set方法。

现在,当基类MasterClass从某个源接收数据时,它会像这样转换/分配它:

//Data came in from an external source, see if we know what variable to assign the value to
public void receiveData(string key, string value)
{
    if (dict.ContainsKey(key))
        assignVal(dict[key], value);
    else
        throw new NotImplementedException(); //use NotImplementedException as placeholder until we make proper exception
}

//Cast the value-string to the proper type and assign it
private void assignVal(HookObj hookobj, string value)
{
    try
    {
        if (hookobj.theType == typeof(string))
            hookobj.setMethod(value);
        else if (hookobj.theType == typeof(int))
            hookobj.setMethod(Int32.Parse(value));
        else if (hookobj.theType == typeof(float))
            hookobj.setMethod(float.Parse(value));
        else
            throw new NotImplementedException();
    }
    catch (RuntimeBinderException ex) { throw new NotImplementedException("", ex); }
    catch (System.FormatException ex) { throw new NotImplementedException("", ex); }
}

HookObj为:

internal class HookObj
{
    public Type theType { get; private set; }
    public Action<dynamic> setMethod { get; private set; }
    public Func<dynamic> getMethod { get; private set; }

    public HookObj(Type theType, Action<dynamic> setMethod, Func<dynamic> getMethod)
    {
        this.theType = theType;
        this.setMethod = setMethod;
        this.getMethod = getMethod;
    }
}

使用getter方法不需要explenation。现在让我们看看这对最终用户来说有多么混乱:

class Attachvariable : MasterClass
{
    public Attachvariable(int id) : base(id) { }
    public Attachvariable(int userID, string position)
        : base()
    {
        this.userID = userID;
        this.position = position;
    }

    int userID;
    string position;

    protected override void hookMethod()
    {
        newHookAndAdd(typeof(int), set_userID, get_userID, "userID").
        newHookAndAdd(typeof(string), set_position, get_position, "position");
    }

    public void set_userID(dynamic theVal)
    {
        userID = theVal;
    }
    public void set_position(dynamic theVal)
    {
        position = theVal;
    }

    public dynamic get_userID()
    {
        return userID;
    }
    public dynamic get_position()
    {
        return position;
    }
}

我希望它更像是这样:

protected override void hookMethod()
{
    newHookAndAdd(ref userID, "userID");
    //etc
}

但是它不可能存储引用供以后使用它似乎..有没有办法让这个用户更友好?每个变量创建两个函数会让我觉得非常混乱。

3 个答案:

答案 0 :(得分:1)

您可以创建一个通用的Hook<T>类来保存该值,并为它保留一个类型的抽象Hook基类,以便MasterClass可以在不知道的情况下获取该类型实际的钩子类型:

public class MasterClass {

  private Dictionary<string, Hook> _dict;

  public Hook<T> AddHook<T>(string name, T value){
    Hook<T> hook = new Hook<T>(value);
    _dict.Add(name, hook);
    return hook;
  }

  public void receiveData(string key, string value) {
    Hook hook;
    if (_dict.TryGetValue(key, out hook)) {
      if (hook._type == typeof(string)) {
        (hook as Hook<string>).Value = value;
      } else if (hook._type == typeof(int)) {
        (hook as Hook<int>).Value = Int32.Parse(value);
      } else {
        throw new NotImplementedException(); // type not found
      }
    } else {
      throw new NotImplementedException(); // name not found
    }
  }

}

public abstract class Hook {
  internal Type _type;
  internal Hook(Type type) {
    _type = type;
  }
}

public class Hook<T> : Hook {
  public T Value { get; set; }
  public Hook(T value) : base(typeof(T)){
    Value = value;
  }
}

现在,用户可以创建Hook<T>个对象来保存值:

class Attachvariable : MasterClass {

  private Hook<int> userId;
  private Hook<string> position;

  private Attachvariable() : base() {
    userId = AddHook("userID", 0);
    position = AddHook("position", String.Empty);
  }

  public Attachvariable(int id, string pos) : this() {
    userId.Value = id;
    position.Value = pos;
  }

}

答案 1 :(得分:1)

我做了类似于@Guffa的回答,但是我没有使用包含属性的“Hook”包装器的对象,而是使用lambdas来使用它们的原始值类型来获取/设置属性:

<强>大师班:

class MasterClass
{
    Dictionary<string, HookObj> dict = new Dictionary<string, HookObj>();

    //Data came in from an external source, see if we know what variable to assign the value to
    public void receiveData(string key, string value)
    {
        if (dict.ContainsKey(key))
            assignVal(dict[key], value);
        else
            throw new NotImplementedException(); //use NotImplementedException as placeholder until we make proper exception
    }

    //Cast the value-string to the proper type and assign it
    private void assignVal(HookObj hookobj, string value)
    {
        try
        {
            if (hookobj.theType == typeof(string))
                hookobj.SetValue(value);
            else if (hookobj.theType == typeof(int))
                hookobj.SetValue(Int32.Parse(value));
            else if (hookobj.theType == typeof(float))
                hookobj.SetValue(float.Parse(value));
            else
                throw new NotImplementedException();
        }
        catch (RuntimeBinderException ex) { throw new NotImplementedException("", ex); }
        catch (System.FormatException ex) { throw new NotImplementedException("", ex); }
    }

    protected void newHookAndAdd<T>(Action<T> setter, Func<T> getter, string name)
    {
        HookObj hook = new HookObj<T>(setter, getter);
        dict.Add(name, hook);
    }
}

<强> HookObj

public class HookObj<T> : HookObj
{
    public Action<T> setMethod { get; private set; }
    public Func<T> getMethod { get; private set; }

    public HookObj(Action<T> setMethod, Func<T> getMethod)
        : base(typeof(T))
    {
        this.setMethod = setMethod;
        this.getMethod = getMethod;
    }

    public override void SetValue(object value)
    {
        setMethod((T)value);
    }

    public override object GetValue()
    {
        return getMethod();
    }
}


public abstract class HookObj
{
    public Type theType { get; private set; }

    public HookObj(Type theType)
    {
        this.theType = theType;
    }

    public abstract void SetValue(object value);
    public abstract object GetValue();
}

然后你的MasterClass的子类:

class Attachvariable : MasterClass
{
    public int UserID { get; set; }
    public string Position { get; set; }

    public Attachvariable()
    {
        hookMethod();
    }

    protected void hookMethod()
    {   
        newHookAndAdd(value => UserID = value, () => UserID, "userID");
        newHookAndAdd(value => Position = value, () => Position, "position");
    }
}

您甚至可以设置您的钩子注册人以提供解析器:

class YesNoVariable : MasterClass
{
    public bool YesNo { get; set; }

    public YesNoVariable()
    {
        hookMethod();
    }

    protected void hookMethod()
    {   
        newHookAndAdd(value => YesNo = value, () => YesNo, "yesno", (input) => input == "yes");
    }
}

我没有通过将解析器作为可选参数添加到基本处理程序的动作,我会留给你,因为它在这个阶段是微不足道的。基本上,您的assignValue会检查HookObj是否分配了解析器,如果已分配,则使用它。否则它会经历你已经拥有的相同动作。

事实上,我希望所有的钩子都使用解析器并具有特定的IntHookObjFloatHookObj等等。 newHookAndAdd方法基本上是一个工厂。如果用户提供自己的解析器,它将使用该自定义解析器创建HookObj。如果T是int / float / string,它将实例化一个已知的HookObj实现,因此您不要将所有解析逻辑与赋值过程混合。

编辑:我最终使用自定义解析器抛出了一个实现,并完全放弃了if / elseif / elseif类型检查:

挂钩基类

public abstract class HookObj<T> : HookObj
{
    public Action<T> setMethod { get; private set; }
    public Func<T> getMethod { get; private set; }


    protected HookObj()
        : base(typeof(T))
    {

    }

    public void SetSetter(Action<T> setMethod)
    {
        this.setMethod = setMethod;
    }

    public void SetGetter(Func<T> getMethod)
    {
        this.getMethod = getMethod;
    }

    protected abstract T Parse(string value);

    public override void SetValue(string value)
    {
        T parsedValue = Parse(value);
        setMethod(parsedValue);
    }

    public override object GetValue()
    {
        return getMethod();
    }
}


public abstract class HookObj
{
    public Type theType { get; private set; }

    public HookObj(Type theType)
    {
        this.theType = theType;
    }

    public abstract void SetValue(string value);
    public abstract object GetValue();
}

标准挂钩实施

public class StringHook : HookObj<string>
{
    protected override string Parse(string value)
    {
        return value;
    }
}

public class IntHook : HookObj<int>
{
    protected override int Parse(string value)
    {
        return Int32.Parse(value);
    }
}

public class FloatHook : HookObj<float>
{
    protected override float Parse(string value)
    {
        return float.Parse(value);
    }
}

自定义挂钩解析器处理程序

public class CustomHook<T> : HookObj<T>
{
    public Func<string, T> inputParser { get; private set; }

    public CustomHook(Func<string, T> parser)
    {
        if (parser == null)
            throw new ArgumentNullException("parser");

        this.inputParser = parser;
    }

    protected override T Parse(string value)
    {
        return inputParser(value);
    }
}

<强>大师班:

class MasterClass
{
    Dictionary<string, HookObj> dict = new Dictionary<string, HookObj>();

    //Data came in from an external source, see if we know what variable to assign the value to
    public void receiveData(string key, string value)
    {
        if (dict.ContainsKey(key))
            assignVal(dict[key], value);
        else
            throw new NotImplementedException(); //use NotImplementedException as placeholder until we make proper exception
    }

    //Cast the value-string to the proper type and assign it
    private void assignVal(HookObj hookobj, string value)
    {
        hookobj.SetValue(value);
    }

    protected void RegisterProperty<T>(Action<T> setter, Func<T> getter, string name, Func<string, T> inputParser)
    {
        var hook = new CustomHook<T>(inputParser);
        hook.SetSetter(setter);
        hook.SetGetter(getter);
        dict.Add(name, hook);
    }

    protected void RegisterProperty(Action<string> setter, Func<string> getter, string name)
    {
        var hook = new StringHook();
        hook.SetSetter(setter);
        hook.SetGetter(getter);
        dict.Add(name, hook);
    }

    protected void RegisterProperty(Action<int> setter, Func<int> getter, string name)
    {
        var hook = new IntHook();
        hook.SetSetter(setter);
        hook.SetGetter(getter);
        dict.Add(name, hook);
    }

    protected void RegisterProperty(Action<float> setter, Func<float> getter, string name)
    {
        var hook = new FloatHook();
        hook.SetSetter(setter);
        hook.SetGetter(getter);
        dict.Add(name, hook);
    }
}

现在,MasterClass可以使用一些工作。具体来说,我感觉 RegisterProperty方法(替换newHookAndAdd)是重复工作,你必须为你支持的每个“标准”钩子类型添加条目。我确信有一个更好的方法,但是现在它提供了你的“Attachvariable”子类,不关心使用什么钩子,他们知道本地支持哪些类型,以及它的类型安全。本地不支持的任何类型,必须提供类型安全的解析器。

附加变量样本

class Attachvariable : MasterClass
{
    public int UserID { get; set; }
    public string Position { get; set; }
    public bool YesNo { get; set; }
    public bool ProperBoolean { get; set; }

    public Attachvariable()
    {
        RegisterProperties();
    }

    protected void RegisterProperties()
    {   
        RegisterProperty(value => UserID = value, () => UserID, "userID");
        RegisterProperty(value => Position = value, () => Position, "position");
        RegisterProperty(value => YesNo = value, () => YesNo, "yesno", (input) => input == "yes");
        RegisterProperty(value => ProperBoolean = value, () => ProperBoolean, "ProperBoolean", (input) => Boolean.Parse(input));
    }
}

原生支持stringint属性;他们遇到与他们的类型相关的RegisterProperty重载。但是,本地不支持bool类型,因此它们提供了自己的解析逻辑。 “yesno”只是检查字符串是否等于“是”。 “ProperBoolean”执行标准Boolean.Parse。用法如下:

Attachvariable obj = new Attachvariable();

obj.receiveData("userID", "9001");
obj.receiveData("position", "Hello World!");
obj.receiveData("yesno", "yes");
obj.receiveData("ProperBoolean", "True");

Console.WriteLine(obj.UserID); //9001
Console.WriteLine(obj.Position); //"Hello World!"
Console.WriteLine(obj.YesNo); //True
Console.WriteLine(obj.ProperBoolean); //True

obj.receiveData("yesno", "something else!");
Console.WriteLine(obj.YesNo); //False

obj.receiveData("ProperBoolean", "Invalid Boolean!"); //throws exception on `Boolean.Parse` step as intended

我想过让“标准”钩子从CustomHook继承而只是向下传递解析方法,但是这样你就可以创建新的标准钩子,这可能有更复杂的解析逻辑,所以这应该让它更容易阅读/实行。无论如何,我鞭打了这个,如果我在生产中使用它,我会花更多的时间来清理/改进/测试。

biiiiiiiig plus这使得非常容易对您的各个钩子类型实现进行单元测试。可能可能重构MasterClass以便更容易进行单元测试(尽管现在非常简单),也许可以将钩子创建者移动到一个单独的工厂/构建器中。

编辑:哎呀,现在只是扔掉theType基地上的HookObj字段并用接口替换它:

public interface IHookObj
{   
    void SetValue(string value);
    object GetValue();
}

您可以在整个过程中传播更改(HookObj<T>不再调用传入typeof(T)的基础构造函数,MasterClass现在绑定到IHookObj接口。这意味着您甚至可以更轻松地定义使用他们想要的任何逻辑,解析或其他方式的钩子,并且应该更容易测试。

编辑:是的,这是一个示例实现,其中API的第三方使用者可以提供他们自己的钩子,可以在他们的应用程序中重复使用。如果他们在任何地方都使用Person个对象,他们只需定义一个钩子就可以重用:

class SomeCustomUsage : MasterClass
{
    public Person SomeoneUnimportant { get; set; }

    public SomeCustomUsage()
    {
        RegisterProperty(value => SomeoneUnimportant = value, () => SomeoneUnimportant, "SomeoneUnimportant", new PersonHook());
    }
}

他们的PersonHook是:

public class PersonHook : HookObj<Person>
{
    protected override Person Parse(string value)
    {
        string[] parts = value.Split(',');
        var person = new Person(parts[0], parts[1]);

        if (person.FirstName == "Bob")
            throw new Exception("You have a silly name and I don't like you.");

        if (String.IsNullOrWhiteSpace(person.FirstName))
            throw new Exception("No first name provided.");

        if (String.IsNullOrWhiteSpace(person.LastName))
            throw new Exception("No last name provided.");

        return person;
    }
}

HookObj<T>提供RegisterProperty重载也将所有重载都折叠为更简单的代码重复(但仍然没有感觉正确):< / p>

protected void RegisterProperty<T>(Action<T> setter, Func<T> getter, string name, HookObj<T> hook)
{
    hook.SetSetter(setter);
    hook.SetGetter(getter);
    dict.Add(name, hook);
}

protected void RegisterProperty<T>(Action<T> setter, Func<T> getter, string name, Func<string, T> inputParser)
{
    var hook = new CustomHook<T>(inputParser);
    RegisterProperty(setter, getter, name, hook);
}

protected void RegisterProperty(Action<string> setter, Func<string> getter, string name)
{
    var hook = new StringHook();
    RegisterProperty(setter, getter, name, hook);
}

protected void RegisterProperty(Action<int> setter, Func<int> getter, string name)
{
    var hook = new IntHook();
    RegisterProperty(setter, getter, name, hook);
}

protected void RegisterProperty(Action<float> setter, Func<float> getter, string name)
{
    var hook = new FloatHook();
    RegisterProperty(setter, getter, name, hook);
}

结果可能如下:

SomeCustomUsage customObj = new SomeCustomUsage();
customObj.receiveData("SomeoneUnimportant", "John,Doe");
Console.WriteLine(customObj.SomeoneUnimportant.LastName + ", " + customObj.SomeoneUnimportant.FirstName); //Doe, John

customObj.receiveData("SomeoneUnimportant", "Bob,Marley"); //exception: "You have a silly name and I don't like you."
customObj.receiveData("SomeoneUnimportant", ",Doe"); //exception: "No first name provided."
customObj.receiveData("SomeoneUnimportant", "John,"); //exception: "No last name provided."

答案 2 :(得分:0)

为什么所有的烟雾和镜子?像这样更直接的事情出了什么问题?

建议1

public abstract class BaseClass
{
    protected virtual int UserId { get; set; }
}

public class ChildClass : BaseClass
{
    private int _userId;

    protected override int UserId
    {
        get { return _userId; }
        set { _userId = value; }
    }
}

建议2

public abstract class BaseClass
{
    protected readonly Dictionary<string, object> Values = new Dictionary<string, object>();
}

public class ChildClass : BaseClass
{
    public ChildClass()
    {
        Values["UserID"] = 123;
        Values["Foo"] = "Bar";
    }
}