OSDN Git Service

C#7.0で追加されたTupleの構文を使用する
authorKimura Youichi <kim.upsilon@bucyou.net>
Sat, 11 Mar 2017 17:34:26 +0000 (02:34 +0900)
committerKimura Youichi <kim.upsilon@bucyou.net>
Sat, 11 Mar 2017 18:45:12 +0000 (03:45 +0900)
ValueTuple 型は .NET Framework のクラスライブラリには含まれていないため自前実装

OpenTween.Tests/Api/MicrosoftTranslatorApiTest.cs
OpenTween.Tests/TweenMainTest.cs
OpenTween.Tests/TwitterTest.cs
OpenTween/Api/MicrosoftTranslatorApi.cs
OpenTween/Api/TwitterApiStatus.cs
OpenTween/OpenTween.csproj
OpenTween/Tween.cs
OpenTween/Twitter.cs
OpenTween/ValueTuple.cs [new file with mode: 0644]

index 5177d63..ba27a6f 100644 (file)
@@ -42,7 +42,7 @@ namespace OpenTween.Api
             {
                 var mock = new Mock<MicrosoftTranslatorApi>(http);
                 mock.Setup(x => x.GetAccessTokenAsync())
-                    .ReturnsAsync(Tuple.Create("1234abcd", TimeSpan.FromSeconds(1000)));
+                    .ReturnsAsync(("1234abcd", TimeSpan.FromSeconds(1000)));
 
                 var translateApi = mock.Object;
 
@@ -78,7 +78,7 @@ namespace OpenTween.Api
         {
             var mock = new Mock<MicrosoftTranslatorApi>();
             mock.Setup(x => x.GetAccessTokenAsync())
-                .ReturnsAsync(Tuple.Create("1234abcd", TimeSpan.FromSeconds(1000)));
+                .ReturnsAsync(("1234abcd", TimeSpan.FromSeconds(1000)));
 
             var translateApi = mock.Object;
 
@@ -113,7 +113,7 @@ namespace OpenTween.Api
         {
             var mock = new Mock<MicrosoftTranslatorApi>();
             mock.Setup(x => x.GetAccessTokenAsync())
-                .ReturnsAsync(Tuple.Create("5678efgh", TimeSpan.FromSeconds(1000)));
+                .ReturnsAsync(("5678efgh", TimeSpan.FromSeconds(1000)));
 
             var translateApi = mock.Object;
             translateApi.AccessToken = "1234abcd";
@@ -165,7 +165,7 @@ namespace OpenTween.Api
                 var result = await translateApi.GetAccessTokenAsync()
                     .ConfigureAwait(false);
 
-                var expectedToken = Tuple.Create(@"12345acbde", TimeSpan.FromSeconds(3600));
+                var expectedToken = (@"12345acbde", TimeSpan.FromSeconds(3600));
                 Assert.Equal(expectedToken, result);
 
                 Assert.Equal(0, mockHandler.QueueCount);
@@ -182,7 +182,7 @@ namespace OpenTween.Api
   ""expires_in"": 3600
 }
 ");
-            var expected = Tuple.Create(@"12345acbde", TimeSpan.FromSeconds(3600));
+            var expected = (@"12345acbde", TimeSpan.FromSeconds(3600));
             Assert.Equal(expected, MicrosoftTranslatorApi.ParseOAuthCredential(jsonBytes));
         }
 
@@ -195,7 +195,7 @@ namespace OpenTween.Api
   ""token_type"": ""bearer""
 }
 ");
-            var expected = Tuple.Create(@"12345acbde", TimeSpan.Zero);
+            var expected = (@"12345acbde", TimeSpan.Zero);
             Assert.Equal(expected, MicrosoftTranslatorApi.ParseOAuthCredential(jsonBytes));
         }
     }
index 49a734a..6843c84 100644 (file)
@@ -41,7 +41,7 @@ namespace OpenTween
             {
                 var data = new DataObject("text/x-moz-url", memstream);
 
-                var expected = new Tuple<string, string>("https://twitter.com/", "Twitter");
+                var expected = ("https://twitter.com/", "Twitter");
                 Assert.Equal(expected, TweenMain.GetUrlFromDataObject(data));
             }
         }
@@ -54,7 +54,7 @@ namespace OpenTween
             {
                 var data = new DataObject("IESiteModeToUrl", memstream);
 
-                var expected = new Tuple<string, string>("https://twitter.com/", "Twitter");
+                var expected = ("https://twitter.com/", "Twitter");
                 Assert.Equal(expected, TweenMain.GetUrlFromDataObject(data));
             }
         }
@@ -67,7 +67,7 @@ namespace OpenTween
             {
                 var data = new DataObject("UniformResourceLocatorW", memstream);
 
-                var expected = new Tuple<string, string>("https://twitter.com/", null);
+                var expected = ("https://twitter.com/", (string)null);
                 Assert.Equal(expected, TweenMain.GetUrlFromDataObject(data));
             }
         }
index 75d55b0..19cc6eb 100644 (file)
@@ -98,9 +98,8 @@ namespace OpenTween
         {
             var sourceHtml = "<a href=\"http://twitter.com\" rel=\"nofollow\">Twitter Web Client</a>";
 
-            var result = Twitter.ParseSource(sourceHtml);
-            Assert.Equal("Twitter Web Client", result.Item1);
-            Assert.Equal(new Uri("http://twitter.com/"), result.Item2);
+            var expected = ("Twitter Web Client", new Uri("http://twitter.com/"));
+            Assert.Equal(expected, Twitter.ParseSource(sourceHtml));
         }
 
         [Fact]
@@ -108,9 +107,8 @@ namespace OpenTween
         {
             var sourceHtml = "web";
 
-            var result = Twitter.ParseSource(sourceHtml);
-            Assert.Equal("web", result.Item1);
-            Assert.Equal(null, result.Item2);
+            var expected = ("web", (Uri)null);
+            Assert.Equal(expected, Twitter.ParseSource(sourceHtml));
         }
 
         [Fact]
@@ -119,9 +117,8 @@ namespace OpenTween
             // 参照: https://twitter.com/kim_upsilon/status/477796052049752064
             var sourceHtml = "<a href=\"erased_45416\" rel=\"nofollow\">erased_45416</a>";
 
-            var result = Twitter.ParseSource(sourceHtml);
-            Assert.Equal("erased_45416", result.Item1);
-            Assert.Equal(new Uri("https://twitter.com/erased_45416"), result.Item2);
+            var expected = ("erased_45416", new Uri("https://twitter.com/erased_45416"));
+            Assert.Equal(expected, Twitter.ParseSource(sourceHtml));
         }
 
         [Fact]
@@ -130,9 +127,8 @@ namespace OpenTween
             // 参照: https://twitter.com/kim_upsilon/status/595156014032244738
             var sourceHtml = "";
 
-            var result = Twitter.ParseSource(sourceHtml);
-            Assert.Equal("", result.Item1);
-            Assert.Equal(null, result.Item2);
+            var expected = ("", (Uri)null);
+            Assert.Equal(expected, Twitter.ParseSource(sourceHtml));
         }
 
         [Fact]
@@ -140,9 +136,8 @@ namespace OpenTween
         {
             string sourceHtml = null;
 
-            var result = Twitter.ParseSource(sourceHtml);
-            Assert.Equal("", result.Item1);
-            Assert.Equal(null, result.Item2);
+            var expected = ("", (Uri)null);
+            Assert.Equal(expected, Twitter.ParseSource(sourceHtml));
         }
 
         [Fact]
@@ -150,9 +145,8 @@ namespace OpenTween
         {
             string sourceHtml = "<a href=\"http://example.com/?aaa=123&amp;bbb=456\" rel=\"nofollow\">&lt;&lt;hogehoge&gt;&gt;</a>";
 
-            var result = Twitter.ParseSource(sourceHtml);
-            Assert.Equal("<<hogehoge>>", result.Item1);
-            Assert.Equal(new Uri("http://example.com/?aaa=123&bbb=456"), result.Item2);
+            var expected = ("<<hogehoge>>", new Uri("http://example.com/?aaa=123&bbb=456"));
+            Assert.Equal(expected, Twitter.ParseSource(sourceHtml));
         }
 
         [Fact]
@@ -160,9 +154,8 @@ namespace OpenTween
         {
             string sourceHtml = "&lt;&lt;hogehoge&gt;&gt;";
 
-            var result = Twitter.ParseSource(sourceHtml);
-            Assert.Equal("<<hogehoge>>", result.Item1);
-            Assert.Equal(null, result.Item2);
+            var expected = ("<<hogehoge>>", (Uri)null);
+            Assert.Equal(expected, Twitter.ParseSource(sourceHtml));
         }
 
         [Fact]
index f6d4ca4..9eca7d9 100644 (file)
@@ -87,16 +87,16 @@ namespace OpenTween.Api
             if (this.AccessToken != null && this.RefreshAccessTokenAt > DateTime.Now)
                 return;
 
-            var accessToken = await this.GetAccessTokenAsync()
+            var (accessToken, expiresIn) = await this.GetAccessTokenAsync()
                 .ConfigureAwait(false);
 
-            this.AccessToken = accessToken.Item1;
+            this.AccessToken = accessToken;
 
             // expires_in の示す時刻より 30 秒早めに再発行する
-            this.RefreshAccessTokenAt = DateTime.Now + accessToken.Item2 - TimeSpan.FromSeconds(30);
+            this.RefreshAccessTokenAt = DateTime.Now + expiresIn - TimeSpan.FromSeconds(30);
         }
 
-        internal virtual async Task<Tuple<string, TimeSpan>> GetAccessTokenAsync()
+        internal virtual async Task<(string AccessToken, TimeSpan ExpiresIn)> GetAccessTokenAsync()
         {
             var param = new Dictionary<string, string>
             {
@@ -121,7 +121,7 @@ namespace OpenTween.Api
             }
         }
 
-        internal static Tuple<string, TimeSpan> ParseOAuthCredential(byte[] responseBytes)
+        internal static (string AccessToken, TimeSpan ExpiresIn) ParseOAuthCredential(byte[] responseBytes)
         {
             using (var jsonReader = JsonReaderWriterFactory.CreateJsonReader(responseBytes, XmlDictionaryReaderQuotas.Max))
             {
@@ -150,7 +150,7 @@ namespace OpenTween.Api
                     expiresInSeconds = 0;
                 }
 
-                return Tuple.Create(accessTokenElm.Value, TimeSpan.FromSeconds(expiresInSeconds));
+                return (accessTokenElm.Value, TimeSpan.FromSeconds(expiresInSeconds));
             }
         }
     }
index ace68dc..5ba3119 100644 (file)
@@ -140,16 +140,16 @@ namespace OpenTween.Api
             var rateLimits =
                 from res in json.Resources
                 from item in res.Value
-                select new {
-                    endpointName = item.Key,
-                    limit = new ApiLimit(
+                select (
+                    EndpointName: item.Key,
+                    Limit: new ApiLimit(
                         item.Value.Limit,
                         item.Value.Remaining,
                         UnixEpoch.AddSeconds(item.Value.Reset).ToLocalTime()
-                    ),
-                };
+                    )
+                );
 
-            this.AccessLimit.AddAll(rateLimits.ToDictionary(x => x.endpointName, x => x.limit));
+            this.AccessLimit.AddAll(rateLimits.ToDictionary(x => x.EndpointName, x => x.Limit));
         }
 
         protected virtual void OnAccessLimitUpdated(AccessLimitUpdatedEventArgs e)
index b0401c5..deb3cb3 100644 (file)
       <DependentUpon>TweetDetailsView.cs</DependentUpon>
     </Compile>
     <Compile Include="TweetExtractor.cs" />
+    <Compile Include="ValueTuple.cs" />
     <Compile Include="WaitingDialog.cs">
       <SubType>Form</SubType>
     </Compile>
index 1ac7310..6bf4f12 100644 (file)
@@ -301,7 +301,7 @@ namespace OpenTween
         }
 
         private Stack<ReplyChain> replyChains; //[, ]でのリプライ移動の履歴
-        private Stack<Tuple<TabPage, PostClass>> selectPostChains = new Stack<Tuple<TabPage, PostClass>>(); //ポスト選択履歴
+        private Stack<ValueTuple<TabPage, PostClass>> selectPostChains = new Stack<ValueTuple<TabPage, PostClass>>(); //ポスト選択履歴
 
         //検索処理タイプ
         internal enum SEARCHTYPE
@@ -6997,17 +6997,17 @@ namespace OpenTween
                     try
                     {
                         this.selectPostChains.Pop();
-                        var tabPostPair = this.selectPostChains.Peek();
+                        var (tabPage, post) = this.selectPostChains.Peek();
 
-                        if (!this.ListTab.TabPages.Contains(tabPostPair.Item1)) continue;  //該当タブが存在しないので無視
+                        if (!this.ListTab.TabPages.Contains(tabPage)) continue;  //該当タブが存在しないので無視
 
-                        if (tabPostPair.Item2 != null)
+                        if (post != null)
                         {
-                            idx = this._statuses.Tabs[tabPostPair.Item1.Text].IndexOf(tabPostPair.Item2.StatusId);
+                            idx = this._statuses.Tabs[tabPage.Text].IndexOf(post.StatusId);
                             if (idx == -1) continue;  //該当ポストが存在しないので無視
                         }
 
-                        tp = tabPostPair.Item1;
+                        tp = tabPage;
 
                         this.selectPostChains.Pop();
                     }
@@ -7044,21 +7044,21 @@ namespace OpenTween
             int count = this.selectPostChains.Count;
             if (count > 0)
             {
-                var p = this.selectPostChains.Peek();
-                if (p.Item1 == this._curTab)
+                var (tabPage, post) = this.selectPostChains.Peek();
+                if (tabPage == this._curTab)
                 {
-                    if (p.Item2 == this._curPost) return;  //最新の履歴と同一
-                    if (p.Item2 == null) this.selectPostChains.Pop();  //置き換えるため削除
+                    if (post == this._curPost) return;  //最新の履歴と同一
+                    if (post == null) this.selectPostChains.Pop();  //置き換えるため削除
                 }
             }
             if (count >= 2500) TrimPostChain();
-            this.selectPostChains.Push(Tuple.Create(this._curTab, this._curPost));
+            this.selectPostChains.Push((this._curTab, this._curPost));
         }
 
         private void TrimPostChain()
         {
             if (this.selectPostChains.Count <= 2000) return;
-            var p = new Stack<Tuple<TabPage, PostClass>>(2000);
+            var p = new Stack<ValueTuple<TabPage, PostClass>>(2000);
             for (int i = 0; i < 2000; i++)
             {
                 p.Push(this.selectPostChains.Pop());
@@ -9617,13 +9617,13 @@ namespace OpenTween
             }
             else if (e.Data.GetDataPresent("UniformResourceLocatorW"))
             {
-                var url = GetUrlFromDataObject(e.Data);
+                var (url, title) = GetUrlFromDataObject(e.Data);
 
                 string appendText;
-                if (url.Item2 == null)
-                    appendText = url.Item1;
+                if (title == null)
+                    appendText = url;
                 else
-                    appendText = url.Item2 + " " + url.Item1;
+                    appendText = title + " " + url;
 
                 if (this.StatusText.TextLength == 0)
                     this.StatusText.Text = appendText;
@@ -9651,7 +9651,7 @@ namespace OpenTween
         /// </remarks>
         /// <exception cref="ArgumentException">不正なフォーマットが入力された場合</exception>
         /// <exception cref="NotSupportedException">サポートされていないデータが入力された場合</exception>
-        internal static Tuple<string, string> GetUrlFromDataObject(IDataObject data)
+        internal static (string Url, string Title) GetUrlFromDataObject(IDataObject data)
         {
             if (data.GetDataPresent("text/x-moz-url"))
             {
@@ -9664,7 +9664,7 @@ namespace OpenTween
                     if (lines.Length < 2)
                         throw new ArgumentException("不正な text/x-moz-url フォーマットです", nameof(data));
 
-                    return new Tuple<string, string>(lines[0], lines[1]);
+                    return (lines[0], lines[1]);
                 }
             }
             else if (data.GetDataPresent("IESiteModeToUrl"))
@@ -9678,7 +9678,7 @@ namespace OpenTween
                     if (lines.Length < 2)
                         throw new ArgumentException("不正な IESiteModeToUrl フォーマットです", nameof(data));
 
-                    return new Tuple<string, string>(lines[0], lines[1]);
+                    return (lines[0], lines[1]);
                 }
             }
             else if (data.GetDataPresent("UniformResourceLocatorW"))
@@ -9688,7 +9688,7 @@ namespace OpenTween
                 using (var stream = (MemoryStream)data.GetData("UniformResourceLocatorW"))
                 {
                     var url = Encoding.Unicode.GetString(stream.ToArray()).TrimEnd('\0');
-                    return new Tuple<string, string>(url, null);
+                    return (url, null);
                 }
             }
 
index 720fe39..78c42f2 100644 (file)
@@ -841,9 +841,9 @@ namespace OpenTween
             post.RetweetedBy = post.RetweetedBy != null ? string.Intern(post.RetweetedBy) : null;
 
             //Source整形
-            var source = ParseSource(sourceHtml);
-            post.Source = string.Intern(source.Item1);
-            post.SourceUri = source.Item2;
+            var (sourceText, sourceUri) = ParseSource(sourceHtml);
+            post.Source = string.Intern(sourceText);
+            post.SourceUri = sourceUri;
 
             post.IsReply = post.ReplyToList.Contains(_uname);
             post.IsExcludeReply = false;
@@ -1593,10 +1593,10 @@ namespace OpenTween
         /// <summary>
         /// Twitter APIから得たHTML形式のsource文字列を分析し、source名とURLに分離します
         /// </summary>
-        public static Tuple<string, Uri> ParseSource(string sourceHtml)
+        internal static (string SourceText, Uri SourceUri) ParseSource(string sourceHtml)
         {
             if (string.IsNullOrEmpty(sourceHtml))
-                return Tuple.Create<string, Uri>("", null);
+                return ("", null);
 
             string sourceText;
             Uri sourceUri;
@@ -1623,7 +1623,7 @@ namespace OpenTween
                 sourceUri = null;
             }
 
-            return Tuple.Create(sourceText, sourceUri);
+            return (sourceText, sourceUri);
         }
 
         public async Task<TwitterApiStatus> GetInfoApi()
diff --git a/OpenTween/ValueTuple.cs b/OpenTween/ValueTuple.cs
new file mode 100644 (file)
index 0000000..6bdbdec
--- /dev/null
@@ -0,0 +1,70 @@
+// OpenTween - Client of Twitter
+// Copyright (c) 2017 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
+// All rights reserved.
+//
+// This file is part of OpenTween.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+// for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program. If not, see <http://www.gnu.org/licenses/>, or write to
+// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
+// Boston, MA 02110-1301, USA.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+// ValueTuple が .NET Framework に追加されるまでの繋ぎのための定義
+
+namespace System
+{
+    internal struct ValueTuple<T1, T2> : IStructuralEquatable
+    {
+        public T1 Item1;
+        public T2 Item2;
+
+        public ValueTuple(T1 item1, T2 item2)
+        {
+            this.Item1 = item1;
+            this.Item2 = item2;
+        }
+
+        bool IStructuralEquatable.Equals(object other, IEqualityComparer comparer)
+        {
+            if (other is ValueTuple<T1, T2> otherTuple)
+            {
+                return comparer.Equals(this.Item1, otherTuple.Item1)
+                    && comparer.Equals(this.Item2, otherTuple.Item2);
+            }
+
+            return false;
+        }
+
+        int IStructuralEquatable.GetHashCode(IEqualityComparer comparer)
+            => comparer.GetHashCode(this.Item1) ^ comparer.GetHashCode(this.Item2);
+    }
+}
+
+namespace System.Runtime.CompilerServices
+{
+    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue | AttributeTargets.Class | AttributeTargets.Struct)]
+    internal class TupleElementNamesAttribute : Attribute
+    {
+        public IList<string> TransformNames { get; }
+
+        public TupleElementNamesAttribute(string[] names)
+            => this.TransformNames = names;
+    }
+}
\ No newline at end of file