【问题标题】:Giving base class access to child-class variables without getter and setter methods在没有 getter 和 setter 方法的情况下让基类访问子类变量
【发布时间】:2012-10-02 21:59:54
【问题描述】:

基类MasterClass 包含一个字典,其中包含一个string 键和一个HookObj 值,其中HookObj 包含(来自派生类)变量类型,并引用它的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 方法的使用不需要解释。所以现在让我们看看这对最终用户来说有多混乱:

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
}

但似乎无法存储参考以供以后使用.. 有什么方法可以使这对用户更加友好?我猜每个变量创建两个函数会变得非常混乱。

【问题讨论】:

  • newHookAndAdd 方法在哪里?我看不到它...
  • @furier 不认为这很重要。我从大师班中删减了很多。该方法所做的只是添加到字典中:newHookAndAdd(Type theType, Action&lt;dynamic&gt; setMethod, Func&lt;dynamic&gt; getMethod, string key) { dict[key] = new HookObj(theType, setMethod, getMethod); }

标签: c# inheritance


【解决方案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”只是检查字符串是否等于“yes”。 “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 的优点是,它非常容易 对您的个人钩子类型实现进行单元测试。可能会重构 MasterClass 以使其更容易进行单元测试(尽管现在很简单),可能会将钩子创建者移动到单独的工厂/构建器中。

编辑:哎呀,现在只需丢弃 HookObj 基础上的 theType 字段并将其替换为接口:

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

您可以在整个过程中传播更改(HookObj&lt;T&gt; 不再调用传入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;
    }
}

RegisterProperty 提供HookObj&lt;T&gt; 重载也将所有重载压缩为更简单的代码重复更少的东西(但它仍然不太感觉正确):

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."

【讨论】:

  • Lambada 表达式仍然让我感到困惑。我几乎无法阅读它们。不过我喜欢你的解析器想法,我明天要试试这个。听起来不错,谢谢!
  • @natli 绝对需要时间来理解它们。当涉及到与 Linq 一起使用的流畅/可读/富有表现力/灵活的代码时,他们认真地打开了一吨大门,并且越来越频繁地使用。我发布了一个带有解析的示例实现。请注意,它完全抛弃了 if/elseif/elseif 类型检查,并且编码人员在编译时就知道支持哪些类型以及他们必须为哪些类型提供自定义解析器。
  • @natli 好的,我已经完成了所有的编辑/更改。我认为这个答案在这一点上已经足够单一了。
  • 刚刚花了一些时间玩弄你的新功能;这很棒! RegisterProperty 方法的数量并不多,但也不是什么大问题。教育答案不应该被称为单一的^^谢谢!
【解决方案2】:

您可以创建一个通用的Hook&lt;T&gt; 类来保存该值,并为其创建一个抽象的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&lt;T&gt; 对象来保存值:

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

}

【讨论】:

  • 这看起来很棒,明天试试(现在太晚了)。如果它按我认为的方式工作,那正是我需要的;)
  • 但我不明白为什么一个人愿意做这一切,只是因为你可以或什么鬼?你对它有什么用?我只是好奇:)
  • @furier 好的,我将复制此评论:关键是子类可以添加任意数量的变量,而基类无需更改即可获取它们.. BaseClass 不知道用户想要添加“int UserID”或其他任何内容,但 baseclass 仍然必须能够使用它。这里的关键是BaseClass中的“receiveData”函数。它对子类的变量进行操作。真的没那么奇怪:P
  • 当您谈论变量时,您正在谈论字段?并且您希望子类在运行时动态添加和删除字段?好吧,字典会用一个键完美地为你做这件事,只是存储对象,可能在一个保存其类型的自定义对象中等等......但无论哪种方式,你要么实例化父类的实例(在其中它将没有关于子类的任何信息)或子类,子类可以访问父类中非私有的所有内容。
  • @furier:“也许在一个自定义对象中,它保存了它的类型等等”——是的,这正是它的工作原理。并且子类获得对该对象的引用,因此它不必在字典中为它挖掘并将值转换为正确的类型。
【解决方案3】:

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

建议 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";
    }
}

【讨论】:

  • 重点是子类可以添加任意数量的变量,而基类无需修改就可以获取它们.. BaseClass 不知道用户想要添加“ int UserID" 或其他任何东西,但基类必须能够使用它,仍然。
  • 是的,它工作得很好,但在你的简单示例中,如果 UserID 不是虚拟的,那么 ChildClass 不需要一行代码......而且由于我唯一可以实例化的类是 ChildClass.. . 但我认为这是出于演示目的:)
  • 好的,我添加的建议 2 怎么样?似乎您希望从基类进行访问,但您希望添加任意变量 - 所以您不会实现完全的类型安全。
  • 您的第二个建议仍然没有以我喜欢的方式解决问题。这种方式对子类来说不那么混乱,但是子类不再有任何“真正的”成员变量。我必须通过 BaseClass 字典访问它们,这也不完全是用户友好的。跨度>
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-12-07
  • 1970-01-01
  • 1970-01-01
  • 2011-09-05
  • 1970-01-01
  • 1970-01-01
  • 2020-12-20
相关资源
最近更新 更多