如何在C#中处理通用的父子结构?

huangapple go评论68阅读模式
英文:

How to handle generic parent child structures in C#?

问题

I'm having a hard time to implement an Api for my Datastructure in C# using generics. Here's what I want to achieve:

static void Main(string[] args) {
    Enterprise google = new Enterprise();
    new ConverterChain<Enterprise>(google)
        .convert(enterprise => enterprise.getWorkers())
        .convert(worker => worker.getAddresses())
        .doAction(address => Debug.Log(address.getStreet()));
}

The fictional simplified example code does the following:

  • find all workers for google and display them on GUI
  • wait for the user to select one worker
  • find all addresses for the selected worker and display them on GUI
  • wait for the user to select one address
  • find all streets for the selected address and display them on GUI
  • wait for the user to select one street and do some action with that street (i.e. write it to log)

As I want to do some asynchronous stuff I need to store all the Funcs and Actions in one datastructure for further execution. In order to have a nice Api, I need to use generics. Here's what I have until now:

public class ConverterChain<I> {
    I input;

    public ConverterChain(I input) {
        this.input = input;
    }

    public Converter<I, O> convert<O>(Func<I, List<O>> c) {
        return new Converter<I, O>(c);
    }
}

public class Converter<I, O> {
    public Func<I, List<O>> c { get; private set; }

    public Converter(Func<I, List<O>> c) {
        this.c = c;
    }

    public Converter<O, N> convert<N>(Func<O, List<N>> c) {
        return new Converter<O, N>(c);
    }

    public void doAction(Action<O> action) {
        // and now bring somehow all converters into a single structure
    }
}

Coming from Java, I would do something like that:

public void doAction(Action<O> action) {
    List<Converter<?, ?>> allConverters = getConvertersFromParents();
    object rootObject = getInputFromRoot();

    GUIHandler.doComplicatedAsynchronousStuff(rootObject, allConverters, action);
}

Without Converter<?, ?> no matter how I implement those data structures, I don't get the step done to convert all the Lambdas into one data structure. I fail to create the parent-child structure in Converter<I, O> in a clean generic way as each converter has different Is and Os.

Using something like Converter<object, object> fails as for whatever reason

Converter<I, O> myConverter = ...
Converter<object, object> genericConverter = (Converter<object, object>) myConverter;

is not allowed.

What is the C# way to implement my given scenario?

英文:

I'm having a hard time to implement an Api for my Datastructure in C# using generics. Here's what I want to achieve:

    static void Main(string[] args) {
        Enterprise google = new Enterprise();
        new ConverterChain&lt;Enterprise&gt;(google)
            .convert(enterprise =&gt; enterprise.getWorkers())
            .convert(worker =&gt; worker.getAddresses())
            .doAction(address =&gt; Debug.Log(address.getStreet()));

    }

The fictional simplified example code does the following:

  • find all workers for google and display them on GUI
  • wait for user to select one worker
  • find all addresses for the selected worker and display them on GUI
  • wait for user to select one address
  • find all streets for the selected address and display them on GUI
  • wait for user to select one street and do some action with that street (i.e. write it to log)

As I want to do some asyncronous stuff I need to store all the Funcs and Actions in one datastructure for further execution. In order to have a nice Api I need to use generics. Here's what I have until now:

public class ConverterChain&lt;I&gt; {
    I input;

    public ConverterChain(I input) {
        this.input = input;
    }

    public Converter&lt;I, O&gt; convert&lt;O&gt;(Func&lt;I, List&lt;O&gt;&gt; c) {
        return new Converter&lt;I, O&gt;(c);
    }
}

public class Converter&lt;I, O&gt; {
    public Func&lt;I, List&lt;O&gt;&gt; c { get; private set; }

    public Converter(Func&lt;I, List&lt;O&gt;&gt; c) {
        this.c = c;
    }

    public Converter&lt;O, N&gt; convert&lt;N&gt;(Func&lt;O, List&lt;N&gt;&gt; c) {
        return new Converter&lt;O, N&gt;(c);
    }

    public void doAction(Action&lt;O&gt; action) {
        // and now bring somehow all converters into a single structure
    }
}

Coming from Java I would do something like that:

public void doAction(Action&lt;O&gt; action) {
    List&lt;Converter&lt;?, ?&gt;&gt; allConverters = getConvertersFromParents();
    object rootObject = getInputFromRoot();

    GUIHandler.doComplicatedAsynchronousStuff(rootObject, allConverters, action);
}

Without Converter&lt;?, ?&gt; no matter how I implement those data structures, I don't get the step done to convert all the Lambdas into one data structure. I fail to create the parent-child structure in Converter&lt;I, O&gt; in a clean generic way as each converter has different Is and Os.

Using something like Converter&lt;object, object&gt; fails as for whatever reason

Converter&lt;I, O&gt; myConverter = ...
Converter&lt;object, object&gt; genericConverter = (Converter&lt;object, object&gt;) myConverter;

is not allowed.

What is the C# way to implement my given scenario?

答案1

得分: 1

I think Java uses type erasure to implement generics. It was a long time I coded java, but I think <?> means "shut off the compile time type checks". In c# the type safety of generic is actually guaranteed by the runtime, so there is no direct equivalence. You could probably extract a list of all the converters if you added a non-generic interface. But you can likely not use Func, each converter would need to actually reference each other. Such an interface would likely have to use object to describe input/output.

However, it seems to me like your code is needlessly complicated. If I were to write an asynchronous version of your specifications I would do something like:

public static async Task<Street> GetStreet(
    Enterprise enterprise, 
    Func<Task<T>, T[]> selection)
{
    var workers = enterprise.GetWorkers();
    var worker = await selection(workers );
    var addresses = worker.GetAddresses();
    var address = await selection(addresses);
    var streets = address.GetStreets();
    var street = await selection(streets);
    return street;
}

That seems to match your functionality description line for line, more so than your code example. You would, of course, need to write a dialog to select items, but I assume that is out of scope.

英文:

I think Java uses type erasure to implement generics. It was a long time I coded java, but I think &lt;?&gt; means "shut of the compile time type checks". In c# the type safety of generic is actually guaranteed by the runtime, so there is no direct equivalence. You could probably extract a list of all the converters if you added a non generic interface. But you can likely not use Func, each converter would need to actually reference each other. Such an interface would likely have to use object to describe input/output.

However, it seem to me like your code is needlessly complicated. If I where to write an asynchronous version of your specifications I would do something like:

public static async Task&lt;Street&gt; GetStreet(
    Enterprise enterprise, 
    Func&lt;Task&lt;T&gt;, T[]&gt; selection)
{
    var workers = enterprise.GetWorkers();
    var worker = await selection(workers );
    var addresses = worker.GetAddresses();
    var address = await selection(addresses);
    var streets = address.GetStreets();
    var street = await selection(streets);
    return street;
}

That seem to match your functionality description line for line, more so than your code example. You would ofc need to write a dialog to select items, but I assume that is out of scope.

答案2

得分: 0

以下是您要翻译的内容:

即使Converter&lt;object, object&gt; genericConverter = (Converter&lt;object, object&gt;) myConverter; 可以编译,但通常情况下它不具备类型安全性(这就是为什么它不能编译的原因,更多信息请查看C#中的协变性与逆变性以及像这个这个的回答)。

您可以这样做 - 引入非泛型基类,以便将任何转换器放入单个集合中。例如,像这样:

public abstract class Converter
{
    public bool CanActOn&lt;T&gt;() =&gt; TryGetActor&lt;T&gt;() 不为 null;
    protected abstract IActOn&lt;T&gt;? TryGetActor&lt;T&gt;();

    public void DoAction&lt;O&gt;(Action&lt;O&gt; action)
    {
        if (TryGetActor&lt;O&gt;() is { } act)
        {
            act.doAction(action);
        }
        else
        {
            // 抛出/忽略?
        }
    }
}

public class Converter&lt;I, O&gt; : Converter, IActOn&lt;O&gt;
{
    // ....

    // 可能要明确实现接口
    public void doAction(Action&lt;O&gt; action) {
        // 然后以某种方式将所有转换器组合到一个结构中
    }

    protected override IActOn&lt;T&gt;? TryGetActor&lt;T&gt;() =&gt; this as IActOn&lt;T&gt;;
}

public interface IActOn&lt;T&gt;
{
    void doAction(Action&lt;T&gt; action);
}

如果目标只是暴露doAction - 那么您可以只使用IActOn&lt;O&gt;

英文:

Even if Converter&lt;object, object&gt; genericConverter = (Converter&lt;object, object&gt;) myConverter; could compile it would not be type-safe in general case (that is the reason why it does not compile, for more - check out variance in C# and answers like this or this one).

What you can do - introduce non-generic base class so you can put any converter in a single collection. For example like this:

public abstract class Converter
{
    public bool CanActOn&lt;T&gt;() =&gt; TryGetActor&lt;T&gt;() is not null;
    protected abstract IActOn&lt;T&gt;? TryGetActor&lt;T&gt;();

    public void DoAction&lt;O&gt;(Action&lt;O&gt; action)
    {
        if (TryGetActor&lt;O&gt;() is { } act)
        {
            act.doAction(action);
        }
        else
        {
            // throw/ignore ?
        }
    }
}

public class Converter&lt;I, O&gt; : Converter, IActOn&lt;O&gt;
{
    // ....

    // maybe make explicit interface implementation
    public void doAction(Action&lt;O&gt; action) {
        // and now bring somehow all converters into a single structure
    }

    protected override IActOn&lt;T&gt;? TryGetActor&lt;T&gt;() =&gt; this as IActOn&lt;T&gt;;
}

public interface IActOn&lt;T&gt;
{
    void doAction(Action&lt;T&gt; action);
}

If the goal is to only have the doAction exposed - then you can just use IActOn&lt;O&gt;.

答案3

得分: 0

The disappointing solution I was able to find was to introduce a Cast-methods converting my generic methods I only required for nice Api into their <object> equivalents.

    internal ConverterChain<object> Cast() {
        return new ConverterChain<object>(input);
    }
}

public class Converter<I, O> { ...
    internal Converter<object, object> Cast() {
        Func<object, List<object>> newC = o => ((List<O>) c.DynamicInvoke(o)).Cast<object>().ToList();
        return new ConverterNode<object, object>(newC);
    }
}

Those <object> equivalents could be used to chain my converters and create the resulting data structure :-/

英文:

The disappointing solution I was able to find was to introduce a Cast-methods converting my generic methods I only required for nice Api into their &lt;object&gt; equivalents.

public class ConverterChain&lt;I&gt; {
    ...

    internal ConverterChain&lt;object&gt; Cast() {
        return new ConverterChain&lt;object&gt;(input);
    }
}

public class Converter&lt;I, O&gt; {
    ...

    internal Converter&lt;object, object&gt; Cast() {
        Func&lt;object, List&lt;object&gt;&gt; newC = o =&gt; ((List&lt;O&gt;) c.DynamicInvoke(o)).Cast&lt;object&gt;().ToList();
        return new ConverterNode&lt;object, object&gt;(newC);
    }
}

Those &lt;object&gt; equivalents could be used to chain my converters and create the resulting data structure :-/

huangapple
  • 本文由 发表于 2023年4月6日 20:10:02
  • 转载请务必保留本文链接:https://go.coder-hub.com/75949371.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定