Asi*_*sik 42 c# generics polymorphism
在这种情况下,使用泛型与接口有什么实际优势:
void MyMethod(IFoo f)
{
}
void MyMethod<T>(T f) : where T : IFoo
{
}
Run Code Online (Sandbox Code Playgroud)
即你能做什么MyMethod<T>,你不能在非通用版本?我正在寻找一个实际的例子,我知道理论上的差异是什么.
我知道,在MyMethod<T>T 中,T将是具体的类型,但是我只能在方法体内将它用作IFoo.那么什么才是真正的优势呢?
Tho*_*que 22
IFoo是值类型,非泛型版本将填充参数的值,而装箱可能会对性能产生负面影响(特别是如果您经常调用此方法)T而不是a IFoo,如果你需要在结果上调用T的方法,这很方便she*_*fly 20
好吧,其他地方提到的一个优点是,如果返回一个值,就能返回特定类型的IFoo类型.但是,由于你的问题是具体的void MyMethod(IFoo f),我想给出一个至少一种情况的现实例子,其中使用泛型方法比对接口更有意义(对我而言).(是的,我花了一些时间在这上面,但我想尝试一些不同的想法.:D)
有两个代码块,第一个是泛型方法本身和一些上下文,第二个是示例的完整代码,包括许多注释,范围从关于此与等效非泛型实现之间可能存在差异的注释,如以及我在实施过程中尝试过的各种不起作用的东西,以及我做出的各种选择的注释,等等...... TL; DR等等.
public class FooChains : Dictionary<IFoo, IEnumerable<IFoo>> { }
// to manage our foos and their chains. very important foo chains.
public class FooManager
{
private FooChains myChainList = new FooChains();
// void MyMethod<T>(T f) where T : IFoo
void CopyAndChainFoo<TFoo>(TFoo fromFoo) where TFoo : IFoo
{
TFoo toFoo;
try {
// create a foo from the same type of foo
toFoo = (TFoo)fromFoo.MakeTyped<TFoo>(EFooOpts.ForChain);
}
catch (Exception Ex) {
// hey! that wasn't the same type of foo!
throw new FooChainTypeMismatch(typeof(TFoo), fromFoo, Ex);
}
// a list of a specific type of foos chained to fromFoo
List<TFoo> typedFoos;
if (!myChainList.Keys.Contains(fromFoo))
{
// no foos there! make a list and connect them to fromFoo
typedChain = new List<TFoo>();
myChainList.Add(fromFoo, (IEnumerable<IFoo>)typedChain);
}
else
// oh good, the chain exists, phew!
typedChain = (List<TFoo>)myChainList[fromFoo];
// add the new foo to the connected chain of foos
typedChain.Add(toFoo);
// and we're done!
}
}
Run Code Online (Sandbox Code Playgroud)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace IFooedYouOnce
{
// IFoo
//
// It's personality is so magnetic, it's erased hard drives.
// It can debug other code... by actually debugging other code.
// It can speak Haskell... in C.
//
// It *is* the most interesting interface in the world.
public interface IFoo
{
// didn't end up using this but it's still there because some
// of the supporting derived classes look silly without it.
bool CanChain { get; }
string FooIdentifier { get; }
// would like to place constraints on this in derived methods
// to ensure type safety, but had to use exceptions instead.
// Liskov yada yada yada...
IFoo MakeTyped<TFoo>(EFooOpts fooOpts);
}
// using IEnumerable<IFoo> here to take advantage of covariance;
// we can have lists of derived foos and just cast back and
// forth for adding or if we need to use the derived interfaces.
// made it into a separate class because probably there will be
// specific operations you can do on the chain collection as a
// whole so this way there's a spot for it instead of, say,
// implementing it all in the FooManager
public class FooChains : Dictionary<IFoo, IEnumerable<IFoo>> { }
// manages the foos. very highly important foos.
public class FooManager
{
private FooChains myChainList = new FooChains();
// would perhaps add a new() constraint here to make the
// creation a little easier; could drop the whole MakeTyped
// method. but was trying to stick with the interface from
// the question.
void CopyAndChainFoo<TFoo>(TFoo fromFoo) where TFoo : IFoo
// void MyMethod<T>(T f) where T : IFoo
{
TFoo toFoo;
// without generics, I would probably create a factory
// method on one of the base classes that could return
// any type, and pass in a type. other ways are possible,
// for instance, having a method which took two IFoos,
// fromFoo and toFoo, and handling the Copy elsewhere.
// could have bypassed this try/catch altogether because
// MakeTyped functions throw if the types are not equal,
// but wanted to make it explicit here. also, this gives
// a more descriptive error which, in general, I prefer
try
{
// MakeTyped<TFoo> was a solution to allowing each TFoo
// to be in charge of creating its own objects
toFoo =
(TFoo)fromFoo.MakeTyped<TFoo>(EFooOpts.ForChain);
}
catch (Exception Ex) {
// tried to eliminate the need for this try/catch, but
// didn't manage. can't constrain the derived classes'
// MakeTyped functions on their own types, and didn't
// want to change the constraints to new() as mentioned
throw
new FooChainTypeMismatch(typeof(TFoo), fromFoo, Ex);
}
// a list of specific type foos to hold the chain
List<TFoo> typedFoos;
if (!myChainList.Keys.Contains(fromFoo))
{
// we just create a new one and link it to the fromFoo
// if none already exists
typedFoos = new List<TFoo>();
myChainList.Add(fromFoo, (IEnumerable<IFoo>)typedFoos);
}
else
// otherwise get the existing one; we are using the
// IEnumerable to hold actual List<TFoos> so we can just
// cast here.
typedFoos = (List<TFoo>)myChainList[fromFoo];
// add it in!
typedFoos.Add(toFoo);
}
}
[Flags]
public enum EFooOpts
{
ForChain = 0x01,
FullDup = 0x02,
RawCopy = 0x04,
Specialize = 0x08
}
// base class, originally so we could have the chainable/
// non chainable distinction but that turned out to be
// fairly pointless since I didn't use it. so, just left
// it like it was anyway so I didn't have to rework all
// the classes again.
public abstract class FooBase : IFoo
{
public string FooIdentifier { get; protected set; }
public abstract bool CanChain { get; }
public abstract IFoo MakeTyped<TFoo>(EFooOpts parOpts);
}
public abstract class NonChainableFoo : FooBase
{
public override bool CanChain { get { return false; } }
}
public abstract class ChainableFoo : FooBase
{
public override bool CanChain { get { return true; } }
}
// not much more interesting to see here; the MakeTyped would
// have been nicer not to exist, but that would have required
// a new() constraint on the chains function.
//
// or would have added "where TFoo : MarkIFoo" type constraint
// on the derived classes' implementation of it, but that's not
// allowed due to the fact that the constraints have to derive
// from the base method, which had to exist on the abstract
// classes to implement IFoo.
public class MarkIFoo : NonChainableFoo
{
public MarkIFoo()
{ FooIdentifier = "MI_-" + Guid.NewGuid().ToString(); }
public override IFoo MakeTyped<TFoo>(EFooOpts fooOpts)
{
if (typeof(TFoo) != typeof(MarkIFoo))
throw new FooCopyTypeMismatch(typeof(TFoo), this, null);
return new MarkIFoo(this, fooOpts);
}
private MarkIFoo(MarkIFoo fromFoo, EFooOpts parOpts) :
this() { /* copy MarkOne foo here */ }
}
public class MarkIIFoo : ChainableFoo
{
public MarkIIFoo()
{ FooIdentifier = "MII-" + Guid.NewGuid().ToString(); }
public override IFoo MakeTyped<TFoo>(EFooOpts fooOpts)
{
if (typeof(TFoo) != typeof(MarkIIFoo))
throw new FooCopyTypeMismatch(typeof(TFoo), this, null);
return new MarkIIFoo(this, fooOpts);
}
private MarkIIFoo(MarkIIFoo fromFoo, EFooOpts parOpts) :
this() { /* copy MarkTwo foo here */ }
}
// yep, really, that's about all.
public class FooException : Exception
{
public Tuple<string, object>[] itemDetail { get; private set; }
public FooException(
string message, Exception inner,
params Tuple<string, object>[] parItemDetail
) : base(message, inner)
{
itemDetail = parItemDetail;
}
public FooException(
string msg, object srcItem, object destType, Exception inner
) : this(msg, inner,
Tuple.Create("src", srcItem), Tuple.Create("dtype", destType)
) { }
}
public class FooCopyTypeMismatch : FooException
{
public FooCopyTypeMismatch(
Type reqDestType, IFoo reqFromFoo, Exception inner
) : base("copy type mismatch", reqFromFoo, reqDestType, inner)
{ }
}
public class FooChainTypeMismatch : FooException
{
public FooChainTypeMismatch(
Type reqDestType, IFoo reqFromFoo, Exception inner
) : base("chain type mismatch", reqFromFoo, reqDestType, inner)
{ }
}
}
// I(Foo) shot J.R.!
Run Code Online (Sandbox Code Playgroud)
Bra*_*vic 18
做这样的事情更容易:
void MyMethod<T>(T f) where T : IFoo, new() {
var t1 = new T();
var t2 = default(T);
// Etc...
}
Run Code Online (Sandbox Code Playgroud)
此外,当您引入更多接口时,泛型可能对呼叫者更"温和".例如,您可以从2个接口继承一个类并直接传递它,就像这样......
interface IFoo {
}
interface IBar {
}
class FooBar : IFoo, IBar {
}
void MyMethod<T>(T f) where T : IFoo, IBar {
}
void Test() {
FooBar fb = new FooBar();
MyMethod(fb);
}
Run Code Online (Sandbox Code Playgroud)
...而"仅接口"方法需要"中间"接口(IFooBar)...
interface IFoo {
}
interface IBar {
}
interface IFooBar : IFoo, IBar {
}
class FooBar : IFooBar {
}
void MyMethod(IFooBar f) {
}
void Test() {
FooBar fb = new FooBar();
MyMethod(fb);
}
Run Code Online (Sandbox Code Playgroud)
2年后,我发现了一个非常简单而有用的案例.考虑这种常见模式:
class MyClass : IDisposable {
public void Dispose() {
if (m_field1 != null) {
m_field1.Dispose();
m_field1 = null;
}
if (m_field2 != null) {
m_field2.Dispose();
m_field2 = null;
}
// etc
}
}
Run Code Online (Sandbox Code Playgroud)
我一直想编写一个帮助方法,以避免为每个字段编写所有这些样板:
class MyClass : IDisposable {
static void IfNotNullDispose(ref IDisposable disposable) {
if (disposable != null) {
disposable.Dispose();
disposable = null;
}
}
public void Dispose() {
IfNotNullDispose(ref m_field1);
IfNotNullDispose(ref m_field2);
// etc
}
}
Run Code Online (Sandbox Code Playgroud)
不幸的是,这在C#中是非法的,因为你不能使用参数的接口,你必须使用你传入的具体类型而不是其他.因此,您必须为要处理的每种类型的字段编写不同的方法.哦等等,这正是泛型为你做的:
static void IfNotNullDispose<T>(ref T disposable) where T: class, IDisposable {
if (disposable != null) {
disposable.Dispose();
disposable = null;
}
}
Run Code Online (Sandbox Code Playgroud)
现在一切都按预期工作!