我有一个WCF消息检查器,用于检查请求和响应:Message
。检查员工作正常。 Message
对象只能读取一次,因此一旦读取它,就不能简单地传播,因为WCF会抱怨已经读取了该消息。因此,我正在创建一个全新的消息副本并传播它。
我设计了一个允许阅读邮件的类,在调用者阅读了他们想要的内容之后,他们需要调用Close
来返回邮件的副本。这是我班级的骨架:
using System.ServiceModel.Channels;
internal abstract class MessageReader
{
internal string ReadSomething(string id)
{
// Return string
}
internal string ReadSomethingElse(string id)
{
// Return string
}
internal Message Close()
{
// Create copy and return it.
}
}
我班级的用户可能忘记拨打Close()
,这很好,因为WCF会对他们大喊大叫。现在我有文档让用户知道他们需要拨打Close()
。
以下是问题
C#的using
构造中是否有一个模式或类似的东西,但最后会返回一个对象?这将非常方便,因为我的类的用户可以只使用这样的结构,最后它将返回消息的副本。像这样:
UsingSomeConstruct(var reader = new MessageReader(ref originalMessage))
{
var a = reader.ReadSomething("something");
var b = reader.ReadSomethingElse("something");
// Do something with what was read
}
// At this point originalMessage will be the copy of the message and no longer the original message.
修改
我考虑过攻击IDisposable
以实现这一目标,但我不打算这样做以寻找其他想法。
答案 0 :(得分:1)
不,没有这样的结构。开箱即用,它太具体了。有一些扩展方法通常非常有用,但您无法在this ref Message
参数上使用它们。
但是,如果您愿意使用ref
,那么为什么不在Reader的构造函数中简单地包含所有逻辑呢?
这是一个有点做作的例子,但它应该表明我的意思。与评论中提到的其他人一样,我也建议在Reader对象上实现IDisposable而不是Close,所以我已经包含了。
TL; DR:在下面的示例中,最重要的是Reader(ref msg)构造函数,它克隆消息,复制数据,并用安全消息类替换原始消息,该类可以多次读取。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
namespace Rextester
{
public class Program
{
public static void Main(string[] args)
{
// real-world variables, keep them typed as base Message
// to be able to silently replace them with different objects
Message original1;
Message original2;
// let's construct some one-time readable messages
{
var tmp1 = new OneTimeMessage();
tmp1.data["mom"] = "dad";
tmp1.data["cat"] = "dog";
original1 = tmp1;
var tmp2 = new OneTimeMessage();
tmp2.data["mom"] = "dad";
tmp2.data["cat"] = "dog";
original2 = tmp2;
}
// test1 - can't read twice
Console.WriteLine("test0A:" + original1.GetData("mom"));
//Console.WriteLine("test0B:" + original1.GetData("mom")); // fail
// test2 - can read twice with Reader's help
var backup1 = original2;
using(var rd1 = new Reader(ref original2))
{
Console.WriteLine("test1A:" + rd1.ReadSomething("mom"));
}
var backup2 = original2;
using(var rd2 = new Reader(ref original2))
{
Console.WriteLine("test1A:" + rd2.ReadSomething("mom"));
//^ ok - becase Reader replaced 'original2' with SafeMessage
}
// test3: Reader's ctor is intelligent
// so no more SafeMessages created during future usage
var backup3 = original2;
using(var rd3 = new Reader(ref original2))
{
}
var backup4 = original2;
using(var rd4 = new Reader(ref original2))
{
}
Console.WriteLine("checking for copies:" + (original2 == backup1));
Console.WriteLine("checking for copies:" + (original2 == backup2));
Console.WriteLine("checking for copies:" + (original2 == backup3));
Console.WriteLine("checking for copies:" + (original2 == backup4));
}
}
}
public abstract class Message
{
public abstract string GetData(string id);
}
public class OneTimeMessage : Message // this models your current one-time-readable message
{
public IDictionary<string, string> data = new Dictionary<string, string>();
public override string GetData(string id)
{
var tmp = data[id];
data.Remove(id);
// that's nonsense, but I want to show that you can't
// read the same thing twice from this object
return tmp;
}
}
public class SafeMessage : Message
{
public IDictionary<string, string> data;
public override String GetData(string id)
{
return data[id];
}
public SafeMessage(Message msg)
{
// read out the full msg's data and store it
// since this is example, we can do it in a pretty simple way
// in your code that will probably be more complex
this.data = new Dictionary<string,string>(((OneTimeMessage)msg).data);
}
}
public class Reader : IDisposable
{
private Message message;
public Reader(ref Message src)
{
src = src is SafeMessage ? src : new SafeMessage(src);
this.message = src;
}
public string ReadSomething(string id){ return message.GetData(id); }
public void Dispose(){ Close(); }
public void Close(){ message=null; Console.WriteLine("reader closed"); }
}
编辑:改进的例子
using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel.Channels;
using System.Text.RegularExpressions;
using System.Xml;
namespace MyProgram
{
public class Program
{
public static void Main(string[] args)
{
// real-world variables, keep them typed as base Message
// to be able to silently replace them with different objects
Message original1;
Message original2;
// let's construct some one-time readable messages
{
original1 = new TheMessage("dad", "dog");
original2 = new TheMessage("dad", "dog");
}
// test1 - can't read twice
Console.WriteLine("test0A:" + original1.GetReaderAtBodyContents().ReadOuterXml());
// Console.WriteLine("test0B:" + original1.GetReaderAtBodyContents().ReadOuterXml()); // fail: InvalidOperationException - it was already read
// test2 - can read ONCE with Reader's help, but the message is replaced and is usable again
var backup1 = original2;
using (var rd1 = new ReaderOnce(ref original2))
{
Console.WriteLine("is message replaced after opening Reader:" + (original2 != backup1));
Console.WriteLine("test1A:" + rd1.ReadBodyXml());
// Console.WriteLine("test1B:" + rd1.ReadBodyXml()); // fail: InvalidOperationException - it was already read
}
// test3 - can read MANY TIMES with ReaderMany's help
// also note we use 'original2' again, which was already used above, so in fact ReaderOnce really works as well
var backup2 = original2;
using (var rd1 = new ReaderMany(ref original2))
{
Console.WriteLine("is message replaced after opening Reader:" + (original2 != backup2));
Console.WriteLine("test2A:" + rd1.ReadBodyXml());
Console.WriteLine("test2B:" + rd1.ReadBodyXml()); // ok
}
Console.WriteLine("Press enter to exit");
Console.ReadLine();
}
}
}
// solution1
public class ReaderOnce : IDisposable
{
private Message localCopy;
public ReaderOnce(ref Message src)
{
// create a WCF MessageBuffer to assist in copying messages
// btw. I suppose you should set some sane limit instead of that below
using (var tempBuffer = src.CreateBufferedCopy(int.MaxValue))
{
src = tempBuffer.CreateMessage(); // FIRST copy for outer use
localCopy = tempBuffer.CreateMessage(); // SECOND copy for internal use in the Reader
}
}
public void Dispose() { Close(); }
public void Close()
{
localCopy.Close(); // but that does NOT affect FIRST copy sent to outer scope outside reader
Console.WriteLine("reader closed");
}
public string ReadBodyXml() // careful: that's again ONE TIME readable
{
return localCopy.GetReaderAtBodyContents().ReadOuterXml();
}
}
// solution2
public class ReaderMany : IDisposable
{
private MessageBuffer localBuffer;
public ReaderMany(ref Message src)
{
localBuffer = src.CreateBufferedCopy(int.MaxValue);
src = localBuffer.CreateMessage(); // FIRST copy for outer use
}
public void Dispose() { Close(); }
public void Close()
{
localBuffer.Close();
Console.WriteLine("reader closed");
}
public string ReadBodyXml() // this is readable multiple times
{
using (var tmp = localBuffer.CreateMessage())
return tmp.GetReaderAtBodyContents().ReadOuterXml();
}
}
// let's fake some Message type to have something to test the Reader on
public class TheMessage : Message
{
public override MessageHeaders Headers => _mh;
public override MessageProperties Properties => _mp;
public override MessageVersion Version => _mv;
private MessageHeaders _mh;
private MessageProperties _mp;
private MessageVersion _mv;
private string data1;
private string data2;
// btw. below: surprise! XmlDictionaryWriter is in "System.Runtime.Serialization", not in "System.Xml"
protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
{
writer.WriteStartElement("foo");
writer.WriteAttributeString("data1", data1);
writer.WriteAttributeString("data2", data2);
writer.WriteEndElement();
}
public TheMessage(string data1, string data2)
{
// remember, this class is just an example, you will work on your own messages you already have
_mv = MessageVersion.Soap12;
_mh = new MessageHeaders(_mv);
_mp = new MessageProperties();
// below: yeah, that's super-naive and wrong, but that's an example
this.data1 = data1;
this.data2 = data2;
}
}
答案 1 :(得分:1)
当然没有这样的语言结构。
我可以建议使用IDisposable
进行清理,并为每个ref Message message
方法添加ReadXXX
参数。我知道这对你的用户来说不太方便,但从另一方面来说,他们不会忘记传递参数。
所以实现将是这样的:
internal class MessageReader : IDisposable
{
private MessageBuffer buffer;
private Message message;
private void Release()
{
if (buffer == null) return;
buffer.Close();
buffer = null;
message = null;
}
protected void OnReadRequest(ref Message message)
{
if (message == null) throw new ArgumentNullException("message");
if (this.message == message) return;
Release();
this.buffer = message.CreateBufferedCopy(int.MaxValue);
message = this.message = buffer.CreateMessage();
}
public void Dispose()
{
Release();
}
internal string ReadSomething(ref Message message, string id)
{
OnReadRequest(ref message);
// Return string
}
internal string ReadSomethingElse(ref Message message, string id)
{
OnReadRequest(ref message);
// Return string
}
}
和样本用法:
using (var reader = new MessageReader())
{
var a = reader.ReadSomething(ref originalMessage, "something");
var b = reader.ReadSomethingElse(ref originalMessage, "something");
// Do something with what was read
}
// At this point originalMessage will be the copy of the message and no longer the original message.
答案 2 :(得分:1)
我这样做的方式如下:
public MessageReader: IDisposable
{
public static MessageReader Create(ref Message message)
{
var buffer = message.CreateBufferedCopy(/*whatever is fit*/);
try
{
var reader = new MessageReader(buffer);
message = buffer.CreateMessage();
return reader;
}
catch
{
buffer.Close();
throw;
}
}
private readonly MessageBuffer buffer;
private bool disposed;
private MessageReader(MessageBuffer buffer) { this.buffer = buffer; }
public void Dispose()
{
if (disposed)
return;
buffer.Close();
disposed = true;
}
public string Read(string id)
{
var newCopy = buffer.CreateMessage();
//work with new copy...
}
}
你只需像这样使用它:
using (var reader = MessageReader.Create(ref message))
//message here is already an untouched copy with no need of user active
//intervention and is never touched again by the reader.
{
var a = reader.Read("something"); //reads copy
...
}
恕我直言,这是尽可能干净的。请注意,MessageReader
仅执行IDisposable
,因为它包含对一次性私有MessageBuffer
的引用。
答案 3 :(得分:1)
感谢@ InBetween,@quetzalcoatl和@Ivan Stoev的所有帮助。赞成你的答案,因为它帮助我达到了以下目的。
在构造函数中,我创建了一条消息,并将原始消息设置为副本。由于此消息的状态为Created
,因此WCF很乐意传播它。我创建了另一个副本,并将其用于多次阅读。
@Ivan说但是如果用户没有要求阅读任何内容那么复制是浪费的工作。这是一个好点,但就我而言,这是一个拦截器,并且所有消息都被拦截以供阅读。
以下是我最后提出的建议代码:
public class MessageReader : IDisposable {
private readonly Message message;
public MessageReader(ref Message originalMessage) {
using( var buffer = originalMessage.CreateBufferedCopy( int.MaxValue ) ) {
// Keep original message for reading
this.message = buffer.CreateMessage();
// Set original message to a copy of the original
originalMessage = buffer.CreateMessage();
}
}
public int ReadSomething(string id) {
// Read from this.message;
}
public int ReadSomethingElse(string id) {
// Read from this.message;
}
public void Close() {
this.Dispose();
}
public void Dispose() {
this.message.Close();
}
}
调用者可以在使用块中使用它,也可以不使用它。使用块是有充分理由而不是黑客。
public object AfterReceiveRequest(ref Message request, IClientChannel channel,
InstanceContext instanceContext) {
try {
using( var rdr = new MessageReader(ref request) ) {
var value= rdr.ReadSomething( someIdentifier );
return value;
}
}
catch( System.Exception ex ) {
throw CreateFault( ex, request );
}
}
答案 4 :(得分:0)
c#中没有语言构造可以满足您的要求。如评论中所述,您可以滥用 // expected results:
// 21250000.022000 -> 21250000.022
// 20.00 -> 20
// 200 -> 200
// 20.50 -> 20.5
var regex = /^[^\.]+?(?=\.0*$)|^[^\.]+?\..*?(?=0*$)|^[^\.]*$/g;
var texts = [
'21250000.022000',
'20.00',
'200',
'20.50'
];
for(var i = 0; i < texts.length; i++) {
var text = texts[i];
console.log(text, '->', text.match(regex)[0]);
}
和语言,并使用IDisposable
块来实现您的目标。
但是,我看不到你正在获得什么,你只是在解决这个问题;现在,用户需要记住使用using
代替using
。后者简单而干净,前者使用一种非常着名的语言结构来做与事物不同的事情,这可能会让人感到非常困惑。