Generics be used to define optional parameters in methods in C#?

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

Can Generics be used to define optional parameters in methods in C#?

问题

我有一个接口,名为 IFoo,它实现了一个方法 BarIFoo 接受一个泛型参数 T,这个泛型参数定义了 Bar 方法接受的参数类型。

public interface IFoo<T>
{
    public void Bar(T value);
}

这很基础。但有时,Bar 不需要接受任何参数。为了实现这一点,我创建了第二个接口,也叫做 IFoo,不接受任何泛型参数。

public interface IFoo
{
    public void Bar();
}

这是有效的,但它增加了每当我想要更新 IFoo 时的开销,因为实际上有两个不同的 IFoo。如果我想要向接口(s) 添加第二个方法,现在我必须要做两次。这也影响了任何实现 IFoo 的类,如果我有一个类 Baz 实现了 IFoo,而我希望它实现 IFoo 的两个版本,我必须定义一个将泛型传递给 IFoo 的版本和一个不传递泛型的版本。例如:

public class Baz<T> : IFoo<T>
{
    private IFoo<T> _command;

    public void Bar(T value) {
        _command.Bar(value);
    }
}
public class Baz : IFoo
{
    private IFoo _command;

    public void Bar() {
        _command.Bar();
    }
}

这两个 Baz 类非常相似。它们都将函数调用传递给另一个 IFoo 对象。但由于一个需要参数,另一个不需要,它们必须是两个不同的类,尽管它们的功能几乎相同。无论我对其中一个进行的更改,我肯定也会对另一个进行更改。最理想的情况是有一个 Baz 类,可以根据它应该接受的参数类型以及是否需要接受参数来实例化。

// 这段代码可以与上面定义的两个版本的 Baz 一起工作。
// 目标是使这段代码只能使用一个 Baz 类。

var intBaz = new Baz<int>();
var stringBaz = new Baz<string>();
var emptyBaz = new Baz();

intBaz.Bar(100);
stringBaz.Bar("如果可能的话,这会非常好,谢谢");
emptyBaz.Bar();

在C#中是否有一种方法可以定义一个接口 IFoo,不仅确定方法 Bar 接受的参数类型,还确定 Bar 是否接受任何参数?谢谢!

到目前为止,我已经能够通过实现两个几乎相同的接口 IFooIFoo<T> 来解决这个问题。这在某种程度上是“有效的”,但使得实现 IFooIFoo<T> 的类难以维护,因为实现一个类需要实现另一个类。对一个类的更改必须反映在另一个类的更改中。最好的情况是增加开发时间。最糟糕的情况是可能会引入代码不同步的漏洞。

英文:

I have an interface, IFoo, that implements one method, Bar. IFoo accepts a generic argument T. This generic argument defines the type of the parameter accepted by Bar.

public interface IFoo&lt;T&gt;
{
    public void Bar(T value);
}

Pretty basic. But, sometimes, it is not necessary for Bar to accept any arguments. To implement this, I have created a second interface, also called IFoo, that does not accept any generic arguments.

public interface IFoo
{
    public void Bar();
}

This works, but it adds overhead anytime I might want to update IFoo, given that there are technically two different IFoos. If I want to add a second method to the interface(s) I now have to do it twice. This also impacts any classes that implement IFoo if I have a class Baz that implements IFoo and I want it to implement both versions of IFoo, I have to define one version of Baz that passes a generic to IFoo and one that does not. For instance:

public class Baz&lt;T&gt; : IFoo&lt;T&gt;
{
    private IFoo&lt;T&gt; _command;

    public void Bar(T value) {
        _command.Bar(value);
    }
}
public class Baz : IFoo
{
    private IFoo _command;

    public void Bar() {
        _command.Bar();
    }
}

Both classes, Baz are fairly similar. They both pass a function call to another IFoo object. But since one takes an argument, and the other does not, they have to be two different classes, despite the fact that their functionality is nearly identical. Whatever changes I make to one I am guaranteed to make to the other. It would be ideal to have one class Baz that can be instantiated with or without a generic depending on what type of argument it is meant to take and whether or not it should even take an argument at all.

// This code works with the two versions of Baz defined above.
// The goal would be to get this code to work with only one Baz class.

var intBaz = new Baz&lt;int&gt;();
var stringBaz = new Baz&lt;string&gt;();
var emptyBaz = new Baz();

intBaz.Bar(100);
stringBaz.Bar(&quot;It would be really nice if this is possible, thank you&quot;);
emptyBaz.Bar();

Is there anyway, within C#, to define an interface IFoo that determines not only what type of argument its method Bar accepts, but also whether or not Bar accepts any argument at all? Thanks!

So far I have been able to work around this problem by implementing two, nearly identical, interfaces IFoo and IFoo&lt;T&gt;. This "works" to an extent but makes classes that implements IFoo and IFoo&lt;T&gt; difficult to maintain, as any class that implements one requires a second class that implements the other. Changes to one class must be reflected in changes to the other. At best, this adds time to development. At worst, this introduces vulnerabilities for the code to become out of sync.

答案1

得分: 2

我会定义一个Unit类型 - 一种只有一个有效值的类型。

struct Unit {}

并在通用接口中使用它,每当你想表示“我不需要参数”时。这样,你可以摆脱非通用接口。

当然,你仍然需要传递new(),但由于这是该类型的唯一值,实际上你没有提供任何信息或进行任何思考。

let emptyBaz = Baz<Unit>::new();
emptyBaz.bar(Unit {});

另一种形式是一个无法实例化的类:

let emptyBaz = Baz<Unit>::new();
emptyBaz.bar(null);

sealed class Unit {
    private Unit() {}
}

这种类型的唯一值是null,因此这可能需要你在实现中进行更多的空值检查。

类似的问题也存在于标准库中,如(Value)Task<T>(Value)Task等。我认为目前尚没有“优雅”的解决方案。如果有的话,标准库可能已经采用了它。

英文:

I would define a Unit type - a type that only has one valid value.

struct Unit {}

And use that on the generic interface whenever you want to say "I don't want arguments". This way, you can get rid of the non-generic interface.

Of course, you still have to pass new(), but since that is the only value for this type, you are not actually giving any information, or doing any thinking.

var emptyBaz = new Baz&lt;Unit&gt;();
emptyBaz.Bar(new());

An alternative formulation is a class that cannot be instantiated:

var emptyBaz = new Baz&lt;Unit&gt;();
emptyBaz.Bar(null);

sealed class Unit {
    private Unit() {}
}

The only value for this type is null, so this might require you to do more null checking in your implementations.

Similar problems exist in the standard library, like (Value)Task&lt;T&gt; and (Value)Task etc. I don't think an "elegant" solution exists yet. If it did, the standard library would have used it.

答案2

得分: 1

你可以使用默认实现来提供第二个方法,这将给实现类一个选项,以便在需要时为两者提供不同的实现:

public interface IFoo<T>
{
    public void Bar(T value);
    
    public void Bar() { Bar(default(T)!); }
}

或者实际上为接口提供默认参数:

public interface IFoo<T>
{
    public void Bar(T value = default(T)!);
}

实际上,不可能同时实现既是泛型类又非泛型类的单个类 - 你必须为此创建两个类和两个接口。你仍然可以在泛型接口中使用默认实现来避免代码重复。

英文:

You can use default implementation to provide the second method, which will give the implementing class an option to have a different implementation for both if needed:

public interface IFoo&lt;T&gt;
{
	public void Bar(T value);
	
	public void Bar() {Bar(default(T)!);}
}

Or actually provide default parameter to the interface:

public interface IFoo&lt;T&gt;
{
	public void Bar(T value = default(T)!);
}

There is really no wayto implement a single class that is and isn't generic at the same time - you have to have two classes for that and two interfaces. You still can use default implementation in the generic interface to avoid code duplication.

huangapple
  • 本文由 发表于 2023年2月8日 09:16:22
  • 转载请务必保留本文链接:https://go.coder-hub.com/75380514.html
匿名

发表评论

匿名网友

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

确定