gpt4 book ai didi

c# - 如何在 C# 中复制 F# 区分的联合类型?

转载 作者:可可西里 更新时间:2023-11-01 08:17:31 29 4
gpt4 key购买 nike

我创建了一个名为 Actor 的新类,用于处理传递给它的消息。我遇到的问题是弄清楚将相关但不同的消息传递给 Actor 的最优雅方式是什么。我的第一个想法是使用继承,但它看起来很臃肿,但它是强类型,这是一个明确的要求。

有什么想法吗?

例子

private abstract class QueueMessage { }

private class ClearMessage : QueueMessage
{
public static readonly ClearMessage Instance = new ClearMessage();

private ClearMessage() { }
}

private class TryDequeueMessage : QueueMessage
{
public static readonly TryDequeueMessage Instance = new TryDequeueMessage();

private TryDequeueMessage() { }
}

private class EnqueueMessage : QueueMessage
{
public TValue Item { get; private set; }

private EnqueueMessage(TValue item)
{
Item = item;
}
}

Actor 类

/// <summary>Represents a callback method to be executed by an Actor.</summary>
/// <typeparam name="TReply">The type of reply.</typeparam>
/// <param name="reply">The reply made by the actor.</param>
public delegate void ActorReplyCallback<TReply>(TReply reply);

/// <summary>Represents an Actor which receives and processes messages in concurrent applications.</summary>
/// <typeparam name="TMessage">The type of message this actor accepts.</typeparam>
/// <typeparam name="TReply">The type of reply made by this actor.</typeparam>
public abstract class Actor<TMessage, TReply> : IDisposable
{
/// <summary>The default total number of threads to process messages.</summary>
private const Int32 DefaultThreadCount = 1;


/// <summary>Used to serialize access to the message queue.</summary>
private readonly Locker Locker;

/// <summary>Stores the messages until they can be processed.</summary>
private readonly System.Collections.Generic.Queue<Message> MessageQueue;

/// <summary>Signals the actor thread to process a new message.</summary>
private readonly ManualResetEvent PostEvent;

/// <summary>This tells the actor thread to stop reading from the queue.</summary>
private readonly ManualResetEvent DisposeEvent;

/// <summary>Processes the messages posted to the actor.</summary>
private readonly List<Thread> ActorThreads;


/// <summary>Initializes a new instance of the Genex.Concurrency&lt;TRequest, TResponse&gt; class.</summary>
public Actor() : this(DefaultThreadCount) { }

/// <summary>Initializes a new instance of the Genex.Concurrency&lt;TRequest, TResponse&gt; class.</summary>
/// <param name="thread_count"></param>
public Actor(Int32 thread_count)
{
if (thread_count < 1) throw new ArgumentOutOfRangeException("thread_count", thread_count, "Must be 1 or greater.");

Locker = new Locker();
MessageQueue = new System.Collections.Generic.Queue<Message>();
EnqueueEvent = new ManualResetEvent(true);
PostEvent = new ManualResetEvent(false);
DisposeEvent = new ManualResetEvent(true);
ActorThreads = new List<Thread>();

for (Int32 i = 0; i < thread_count; i++)
{
var thread = new Thread(ProcessMessages);
thread.IsBackground = true;
thread.Start();
ActorThreads.Add(thread);
}
}


/// <summary>Posts a message and waits for the reply.</summary>
/// <param name="value">The message to post to the actor.</param>
/// <returns>The reply from the actor.</returns>
public TReply PostWithReply(TMessage message)
{
using (var wrapper = new Message(message))
{
lock (Locker) MessageQueue.Enqueue(wrapper);
PostEvent.Set();
wrapper.Channel.CompleteEvent.WaitOne();
return wrapper.Channel.Value;
}
}

/// <summary>Posts a message to the actor and executes the callback when the reply is received.</summary>
/// <param name="value">The message to post to the actor.</param>
/// <param name="callback">The callback that will be invoked once the replay is received.</param>
public void PostWithAsyncReply(TMessage value, ActorReplyCallback<TReply> callback)
{
if (callback == null) throw new ArgumentNullException("callback");
ThreadPool.QueueUserWorkItem(state => callback(PostWithReply(value)));
}

/// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
public void Dispose()
{
if (DisposeEvent.WaitOne(10))
{
DisposeEvent.Reset();
PostEvent.Set();

foreach (var thread in ActorThreads)
{
thread.Join();
}

((IDisposable)PostEvent).Dispose();
((IDisposable)DisposeEvent).Dispose();
}
}

/// <summary>Processes a message posted to the actor.</summary>
/// <param name="message">The message to be processed.</param>
protected abstract void ProcessMessage(Message message);

/// <summary>Dequeues the messages passes them to ProcessMessage.</summary>
private void ProcessMessages()
{
while (PostEvent.WaitOne() && DisposeEvent.WaitOne(10))
{
var message = (Message)null;

while (true)
{
lock (Locker)
{
message = MessageQueue.Count > 0 ?
MessageQueue.Dequeue() :
null;

if (message == null)
{
PostEvent.Reset();
break;
}
}

try
{
ProcessMessage(message);
}
catch
{

}
}
}
}


/// <summary>Represents a message that is passed to an actor.</summary>
protected class Message : IDisposable
{
/// <summary>The actual value of this message.</summary>
public TMessage Value { get; private set; }

/// <summary>The channel used to give a reply to this message.</summary>
public Channel Channel { get; private set; }


/// <summary>Initializes a new instance of Genex.Concurrency.Message class.</summary>
/// <param name="value">The actual value of the message.</param>
public Message(TMessage value)
{
Value = value;
Channel = new Channel();
}


/// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
public void Dispose()
{
Channel.Dispose();
}
}

/// <summary>Represents a channel used by an actor to reply to a message.</summary>
protected class Channel : IDisposable
{
/// <summary>The value of the reply.</summary>
public TReply Value { get; private set; }

/// <summary>Signifies that the message has been replied to.</summary>
public ManualResetEvent CompleteEvent { get; private set; }


/// <summary>Initializes a new instance of Genex.Concurrency.Channel class.</summary>
public Channel()
{
CompleteEvent = new ManualResetEvent(false);
}

/// <summary>Reply to the message received.</summary>
/// <param name="value">The value of the reply.</param>
public void Reply(TReply value)
{
Value = value;
CompleteEvent.Set();
}

/// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
public void Dispose()
{
((IDisposable)CompleteEvent).Dispose();
}
}
}

最佳答案

Steve Gilham 总结了编译器实际上是如何处理可区分联合的。对于您自己的代码,您可以考虑其简化版本。给定以下 F#:

type QueueMessage<T> = ClearMessage | TryDequeueMessage | EnqueueMessage of T

这是在 C# 中模拟它的一种方法:

public enum MessageType { ClearMessage, TryDequeueMessage, EnqueueMessage }

public abstract class QueueMessage<T>
{
// prevents unwanted subclassing
private QueueMessage() { }

public abstract MessageType MessageType { get; }

/// <summary>
/// Only applies to EnqueueMessages
/// </summary>
public abstract T Item { get; }

public static QueueMessage<T> MakeClearMessage() { return new ClearMessage(); }
public static QueueMessage<T> MakeTryDequeueMessage() { return new TryDequeueMessage(); }
public static QueueMessage<T> MakeEnqueueMessage(T item) { return new EnqueueMessage(item); }


private sealed class ClearMessage : QueueMessage<T>
{
public ClearMessage() { }

public override MessageType MessageType
{
get { return MessageType.ClearMessage; }
}

/// <summary>
/// Not implemented by this subclass
/// </summary>
public override T Item
{
get { throw new NotImplementedException(); }
}
}

private sealed class TryDequeueMessage : QueueMessage<T>
{
public TryDequeueMessage() { }

public override MessageType MessageType
{
get { return MessageType.TryDequeueMessage; }
}

/// <summary>
/// Not implemented by this subclass
/// </summary>
public override T Item
{
get { throw new NotImplementedException(); }
}
}

private sealed class EnqueueMessage : QueueMessage<T>
{
private T item;
public EnqueueMessage(T item) { this.item = item; }

public override MessageType MessageType
{
get { return MessageType.EnqueueMessage; }
}

/// <summary>
/// Gets the item to be enqueued
/// </summary>
public override T Item { get { return item; } }
}
}

现在,在给定 QueueMessage 的代码中,您可以打开 MessageType 属性来代替模式匹配,并确保您访问 Item 属性仅在 EnqueueMessage 上。

编辑

这是另一种选择,基于 Juliet 的代码。不过,我已经尝试简化一些事情,以便它从 C# 中获得一个更有用的界面。这比以前的版本更可取,因为您无法获得 MethodNotImplemented 异常。

public abstract class QueueMessage<T>
{
// prevents unwanted subclassing
private QueueMessage() { }

public abstract TReturn Match<TReturn>(Func<TReturn> clearCase, Func<TReturn> tryDequeueCase, Func<T, TReturn> enqueueCase);

public static QueueMessage<T> MakeClearMessage() { return new ClearMessage(); }
public static QueueMessage<T> MakeTryDequeueMessage() { return new TryDequeueMessage(); }
public static QueueMessage<T> MakeEnqueueMessage(T item) { return new EnqueueMessage(item); }

private sealed class ClearMessage : QueueMessage<T>
{
public ClearMessage() { }

public override TReturn Match<TReturn>(Func<TReturn> clearCase, Func<TReturn> tryDequeueCase, Func<T, TReturn> enqueueCase)
{
return clearCase();
}
}

private sealed class TryDequeueMessage : QueueMessage<T>
{
public TryDequeueMessage() { }

public override TReturn Match<TReturn>(Func<TReturn> clearCase, Func<TReturn> tryDequeueCase, Func<T, TReturn> enqueueCase)
{
return tryDequeueCase();
}
}

private sealed class EnqueueMessage : QueueMessage<T>
{
private T item;
public EnqueueMessage(T item) { this.item = item; }

public override TReturn Match<TReturn>(Func<TReturn> clearCase, Func<TReturn> tryDequeueCase, Func<T, TReturn> enqueueCase)
{
return enqueueCase(item);
}
}
}

您可以像这样使用此代码:

public class MessageUserTest
{
public void Use()
{
// your code to get a message here...
QueueMessage<string> msg = null;

// emulate pattern matching, but without constructor names
int i =
msg.Match(
clearCase: () => -1,
tryDequeueCase: () => -2,
enqueueCase: s => s.Length);
}
}

关于c# - 如何在 C# 中复制 F# 区分的联合类型?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2320919/

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