是否存在类似于C#`using`的构造或模式,它将返回一个对象?

时间:2017-01-03 18:40:00

标签: c# wcf

我有一个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以实现这一目标,但我不打算这样做以寻找其他想法。

5 个答案:

答案 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。后者简单而干净,前者使用一种非常着名的语言结构来做与事物不同的事情,这可能会让人感到非常困惑。