gpt4 book ai didi

c# - 有没有一种简单的方法可以在C#中模拟Objective-C类别?

转载 作者:太空狗 更新时间:2023-10-30 03:45:25 26 4
gpt4 key购买 nike

我有一个以前从未遇到过的怪异的设计情况...如果使用的是Objective-C,我可以使用类别来解决它,但是我必须使用C#2.0。

首先,一些背景。我在这个类库中有两个抽象层。底层为扫描内容的组件实现了插件架构(抱歉,不能比这更具体)。每个插件都将以某种独特的方式进行扫描,但是插件也会因接受的内容类型而异。由于各种与本次讨论无关的原因,我不想通过插件界面公开泛型。因此,我最终得到了每种内容类型的IScanner接口(interface)和派生接口(interface)。

顶层是一个便利包装,它接受包含各个部分的复合内容格式。不同的扫描仪将需要复合 Material 的不同部分,具体取决于它们感兴趣的内容类型。因此,我需要具有特定于每个IScanner派生接口(interface)的逻辑来解析复合 Material 内容,以查找所需的相关部分。

解决此问题的一种方法是简单地向IScanner添加另一种方法,并在每个插件中实现它。但是,两层设计的重点是插件本身不需要了解复合格式。解决此问题的蛮力方法是在上层进行类型测试和下转换,但是随着将来增加对新内容类型的支持,这些内容将需要仔细维护。在这种情况下,访问者模式也会很尴尬,因为实际上只有一个访问者,但是不同的可访问类型的数量只会随着时间而增加(即,这是适合访问者的相反条件)。另外,当我真正想要的是劫持IScanner的单派调度时,双派调度感觉就像是过分杀了!

如果使用的是Objective-C,则只需在每个IScanner派生的接口(interface)上定义一个类别,然后在其中添加parseContent方法。类别将在上层定义,因此无需更改插件,同时避免了类型测试的需要。不幸的是,C#扩展方法因为基本上是静态的而无法工作(即-与调用站点上使用的引用的编译时类型相关联,而不是像Obj-C Categories那样挂接到动态分配中)。更不用说,我必须使用C#2.0,所以扩展方法甚至对我都不可用。 :-P

那么,有没有一种干净,简单的方法可以解决C#中的问题,类似于用Objective-C类别解决问题的方法呢?

编辑:一些伪代码可以帮助使当前设计的结构变得清晰:

interface IScanner
{ // Nothing to see here...
}

interface IContentTypeAScanner : IScanner
{
void ScanTypeA(TypeA content);
}

interface IContentTypeBScanner : IScanner
{
void ScanTypeB(TypeB content);
}

class CompositeScanner
{
private readonly IScanner realScanner;

// C-tor omitted for brevity... It takes an IScanner that was created
// from an assembly-qualified type name using dynamic type loading.

// NOTE: Composite is defined outside my code and completely outside my control.
public void ScanComposite(Composite c)
{
// Solution I would like (imaginary syntax borrowed from Obj-C):
// [realScanner parseAndScanContentFrom: c];
// where parseAndScanContentFrom: is defined in a category for each
// interface derived from IScanner.

// Solution I am stuck with for now:
if (realScanner is IContentTypeAScanner)
{
(realScanner as IContentTypeAScanner).ScanTypeA(this.parseTypeA(c));
}
else if (realScanner is IContentTypeBScanner)
{
(realScanner as IContentTypeBScanner).ScanTypeB(this.parseTypeB(c));
}
else
{
throw new SomeKindOfException();
}
}

// Private parsing methods omitted for brevity...
}

编辑:澄清一下,我已经考虑了很多设计。我有很多原因,其中大部分我无法分享,这就是为什么。我还没有接受任何答案,因为尽管有趣,但它们回避了原始问题。

事实是,在Obj-C中,我可以简单,优雅地解决此问题。问题是,我可以在C#中使用相同的技术吗?我不介意寻找替代品,但公平地说,这不是我问的问题。 :)

最佳答案

听起来您在说的是您的内容布局如下:

+--------+| part 1 || type A |+--------+| part 2 || type C |+--------+| part 3 || type F |+--------+| part 4 || type D |+--------+

and you have readers for each part type. That is, an AScanner knows how to process the data in a part of type A (such as part 1 above), a BScanner knows how to process the data in a part of type B, and so forth. Am I right so far?

Now, if I understand you, the problem that you're having is that the type readers (the IScanner implementations) don't know how to locate the part(s) they recognize within your composite container.

Can your composite container correctly enumerate the separate parts (i.e., does it know where one part ends and another begins), and if so, does each part have some sort of identification that either the scanner or the container can differentiate?

What I mean is, is the data laid out something like this?

+-------------+| part 1      || length: 100 || type: "A"   || data: ...   |+-------------+| part 2      || length: 460 || type: "C"   || data: ...   |+-------------+| part 3      || length: 26  || type: "F"   || data: ...   |+-------------+| part 4      || length: 790 || type: "D"   || data: ...   |+-------------+

If your data layout is similar to this, could the scanners not request of the container all parts with an identifier matching a given pattern? Something like:

class Container : IContainer{
IEnumerable IContainer.GetParts(string type){
foreach(IPart part in internalPartsList)
if(part.TypeID == type)
yield return part;
}
}

class AScanner : IScanner{
void IScanner.ProcessContainer(IContainer c){
foreach(IPart part in c.GetParts("A"))
ProcessPart(part);
}
}

或者,如果容器可能无法识别零件类型,但扫描仪将能够识别其自己的零件类型,则可能是这样的:
delegate void PartCallback(IPart part);

class Container : IContainer{
void IContainer.GetParts(PartCallback callback){
foreach(IPart part in internalPartsList)
callback(part);
}
}

class AScanner : IScanner{
void IScanner.ProcessContainer(IContainer c){
c.GetParts(delegate(IPart part){
if(IsTypeA(part))
ProcessPart(part);
});
}

bool IsTypeA(IPart part){
// determine if part is of type A
}
}

也许我误解了您的内容和/或体系结构。如果是这样,请澄清一下,我会进行更新。

OP的评论:

  1. The scanners should not have any knowledge of the container type.
  2. The container type has no built-in intelligence. It is as close to plain old data as you can get in C#.
  3. I can't change the container type; It is part of an existing architecture.


我的回复太长了,无法发表评论:
  • 扫描仪必须采用某种方式来检索其处理的零件。如果您担心IScanner接口(interface)不应该知道IContainer接口(interface),以便将来有自由更改IContainer接口(interface)的权限,则可以采用以下两种方法之一进行折衷:
  • 您可以向扫描仪传递一个IPartProvider接口(interface),该接口(interface)是IContainer派生自(或包含)的。该IPartProvider仅提供提供零件服务的功能,因此它应该非常稳定,并且可以在与IScanner相同的程序集中定义,因此您的插件无需引用定义了IContainer的程序集。
  • 您可以将委托(delegate)传递给扫描仪,以使他们可以用来检索零件。然后,扫描程序将只需要委托(delegate)者就不需要任何接口(interface)的知识(当然,除了IScanner除外)。
  • 听起来好像您需要一个替代类,该类知道如何与容器和扫描仪进行通信。只要容器已经公开公开了足够的功能(或 protected [这是一个单词?]),那么上面提到的任何功能都可以在任何ol'类中实现,以使外部/派生类能够访问相关数据。


  • 从您编辑过的问题中的伪代码来看,您似乎并没有真正从界面中获得任何好处,并且正在紧密将您的插件耦合到您的主应用程序中,因为每种扫描器类型都具有唯一的 IScanner派生形式,即定义了唯一扫描”方法,并且 CompositeScanner类对每种零件类型都有唯一的“解析”方法。

    我会说这是您的主要问题。您需要将插件(我假设是 IScanner接口(interface)的实现者)与主应用程序分离,而我假设这是 CompositeScanner类所在的地方。我较早的建议之一是如何实现该目标,但具体细节取决于 parseType X函数的工作方式。这些可以抽象和概括吗?

    据推测,您的 parseType X函数与 Composite类对象进行通信以获得所需的数据。是否可以将它们移到通过 Parse类代理的 IScanner接口(interface)上的 CompositeScanner方法中,以便从 Composite对象获取此数据?像这样的东西:
    delegate byte[] GetDataHandler(int offset, int length);

    interface IScanner{
    void Scan(byte[] data);
    byte[] Parse(GetDataHandler getData);
    }

    class Composite{
    public byte[] GetData(int offset, int length){/*...*/}
    }

    class CompositeScanner{}
    IScanner realScanner;

    public void ScanComposite(Composite c){
    realScanner.Scan(realScanner.Parse(delegate(int offset, int length){
    return c.GetData(offset, length);
    });
    }
    }

    当然,可以通过删除 Parse上的单独 IScanner方法并将简单的 GetDataHandler委托(delegate)直接传递给 Scan来简化此操作(如果需要,其实现可以调用私有(private) Parse)。然后,代码看起来与我之前的示例非常相似。

    这种设计提供了我可以想到的关注点和去耦的尽可能多的分离。

    我只是想到了其他一些东西,您可能会觉得更可口,并且确实可以更好地分离关注点。

    如果每个插件都可以在应用程序中“注册”,则可以在应用程序内进行解析,只要该插件可以告诉应用程序如何检索其数据即可。下面是示例,但是由于我不知道如何识别零件,因此我实现了两种可能性-一种用于索引零件,一种用于命名零件:
    // parts identified by their offset within the file
    class MainApp{
    struct BlockBounds{
    public int offset;
    public int length;

    public BlockBounds(int offset, int length){
    this.offset = offset;
    this.length = length;
    }
    }

    Dictionary<Type, BlockBounds> plugins = new Dictionary<Type, BlockBounds>();

    public void RegisterPlugin(Type type, int offset, int length){
    plugins[type] = new BlockBounds(offset, length);
    }

    public void ScanContent(Container c){
    foreach(KeyValuePair<Type, int> pair in plugins)
    ((IScanner)Activator.CreateInstance(pair.Key)).Scan(
    c.GetData(pair.Value.offset, pair.Value.length);
    }
    }

    或者
    // parts identified by name, block length stored within content (as in diagram above)
    class MainApp{
    Dictionary<string, Type> plugins = new Dictionary<string, Type>();

    public void RegisterPlugin(Type type, string partID){
    plugins[partID] = type;
    }

    public void ScanContent(Container c){
    foreach(IPart part in c.GetParts()){
    Type type;
    if(plugins.TryGetValue(part.ID, out type))
    ((IScanner)Activator.CreateInstance(type)).Scan(part.Data);
    }
    }
    }

    显然,我已经极大地简化了这些示例,但是希望您能理解。另外,如果您可以将工厂(或工厂委托(delegate))传递给 Activator.CreateInstance方法,那么最好不要使用 RegisterPlugin

    关于c# - 有没有一种简单的方法可以在C#中模拟Objective-C类别?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/421119/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com